diff options
613 files changed, 21030 insertions, 4343 deletions
diff --git a/Android.bp b/Android.bp index 838f304b2e8e..e03f8449891a 100644 --- a/Android.bp +++ b/Android.bp @@ -169,7 +169,6 @@ java_library_with_nonpublic_deps { "framework-statsd.impl", "framework-supplementalprocess.impl", "framework-tethering.impl", - "framework-nearby.impl", "framework-uwb.impl", "framework-wifi.impl", "updatable-media", diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index 9b1f2d07d58a..13ecd25d429a 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -3,6 +3,7 @@ package com.android.server.usage; import android.annotation.CurrentTimeMillisLong; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.ActivityManager.ProcessState; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageStatsManager.ForcedReasons; import android.app.usage.UsageStatsManager.StandbyBuckets; @@ -216,4 +217,19 @@ public interface AppStandbyInternal { void dumpState(String[] args, PrintWriter pw); boolean isAppIdleEnabled(); + + /** + * Returns the duration (in millis) for the window where events occurring will be + * considered as broadcast response, starting from the point when an app receives + * a broadcast. + */ + long getBroadcastResponseWindowDurationMs(); + + /** + * Returns the process state threshold that should be used for deciding whether or not an app + * is in the background in the context of recording broadcast response stats. Apps whose + * process state is higher than this threshold state should be considered to be in background. + */ + @ProcessState + int getBroadcastResponseFgThresholdState(); } diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 8b2639781181..b843dcaaf680 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -347,6 +347,22 @@ public class AppStandbyController */ boolean mLinkCrossProfileApps = ConstantsObserver.DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS; + + /** + * Duration (in millis) for the window where events occurring will be considered as + * broadcast response, starting from the point when an app receives a broadcast. + */ + volatile long mBroadcastResponseWindowDurationMillis = + ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS; + + /** + * Process state threshold that is used for deciding whether or not an app is in the background + * in the context of recording broadcast response stats. Apps whose process state is higher + * than this threshold state will be considered to be in background. + */ + volatile int mBroadcastResponseFgThresholdState = + ConstantsObserver.DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE; + /** * Whether we should allow apps into the * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket or not. @@ -1774,6 +1790,15 @@ public class AppStandbyController } } + @Override + public long getBroadcastResponseWindowDurationMs() { + return mBroadcastResponseWindowDurationMillis; + } + + @Override + public int getBroadcastResponseFgThresholdState() { + return mBroadcastResponseFgThresholdState; + } @Override public void flushToDisk() { @@ -2042,6 +2067,14 @@ public class AppStandbyController TimeUtils.formatDuration(mSystemUpdateUsageTimeoutMillis, pw); pw.println(); + pw.print(" mBroadcastResponseWindowDurationMillis="); + TimeUtils.formatDuration(mBroadcastResponseWindowDurationMillis, pw); + pw.println(); + + pw.print(" mBroadcastResponseFgThresholdState="); + pw.print(ActivityManager.procStateToString(mBroadcastResponseFgThresholdState)); + pw.println(); + pw.println(); pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled); pw.print(" mAllowRestrictedBucket="); @@ -2473,6 +2506,10 @@ public class AppStandbyController KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "rare", KEY_PREFIX_ELAPSED_TIME_THRESHOLD + "restricted" }; + private static final String KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS = + "broadcast_response_window_timeout_ms"; + private static final String KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE = + "broadcast_response_fg_threshold_state"; public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS = COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR; public static final long DEFAULT_STRONG_USAGE_TIMEOUT = @@ -2502,6 +2539,10 @@ public class AppStandbyController public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS = COMPRESS_TIME ? ONE_MINUTE : ONE_DAY; public static final boolean DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = true; + public static final long DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS = + 2 * ONE_MINUTE; + public static final int DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE = + ActivityManager.PROCESS_STATE_TOP; ConstantsObserver(Handler handler) { super(handler); @@ -2619,6 +2660,16 @@ public class AppStandbyController KEY_UNEXEMPTED_SYNC_SCHEDULED_HOLD_DURATION, DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT); break; + case KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS: + mBroadcastResponseWindowDurationMillis = properties.getLong( + KEY_BROADCAST_RESPONSE_WINDOW_DURATION_MS, + DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS); + break; + case KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE: + mBroadcastResponseFgThresholdState = properties.getInt( + KEY_BROADCAST_RESPONSE_FG_THRESHOLD_STATE, + DEFAULT_BROADCAST_RESPONSE_FG_THRESHOLD_STATE); + break; default: if (!timeThresholdsUpdated && (name.startsWith(KEY_PREFIX_SCREEN_TIME_THRESHOLD) diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING index c5dc51cc9c24..e407e3126058 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING @@ -5,7 +5,9 @@ "options": [ {"include-filter": "android.app.usage.cts.UsageStatsTest"}, {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"} + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.MediumTest"}, + {"exclude-annotation": "androidx.test.filters.LargeTest"} ] }, { diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index d963e68d80ec..b4edd39894f9 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -61,6 +61,9 @@ java_library { "test_com.android.media", ], min_sdk_version: "29", + lint: { + strict_updatability_linting: true, + }, visibility: [ "//frameworks/av/apex:__subpackages__", "//frameworks/base", // For framework-all diff --git a/apex/media/framework/lint-baseline.xml b/apex/media/framework/lint-baseline.xml index e1b145083f80..95eea45069ef 100644 --- a/apex/media/framework/lint-baseline.xml +++ b/apex/media/framework/lint-baseline.xml @@ -1,312 +1,70 @@ <?xml version="1.0" encoding="UTF-8"?> -<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0"> +<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev"> <issue - id="NewApi" - message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`" - errorLine1=" new ApplicationMediaCapabilities.Builder();" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + id="DefaultLocale" + message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`." + errorLine1=" if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {" + errorLine2=" ~~~~~~~~~~~"> <location file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java" - line="208" - column="29"/> + line="121" + column="57"/> </issue> <issue - id="NewApi" - message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`" - errorLine1=" ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder();" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + id="DefaultLocale" + message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead" + errorLine1=" return String.format(" session: {id: %d, status: %s, result: %s, progress: %d}"," + errorLine2=" ^"> <location - file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java" - line="314" - column="56"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level R (current min is 29): `android.os.RemoteException#rethrowFromSystemServer`" - errorLine1=" e.rethrowFromSystemServer();" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaCommunicationManager.java" - line="110" - column="15"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level R (current min is 29): `android.os.Parcel#writeParcelableCreator`" - errorLine1=" dest.writeParcelableCreator((Parcelable) parcelable);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java" - line="77" - column="14"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level R (current min is 29): `android.os.Parcel#readParcelableCreator`" - errorLine1=" return from.readParcelableCreator(loader);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java" - line="82" - column="21"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#mediaFormat`" - errorLine1=" this.mediaFormat = mediaFormat;" - errorLine2=" ~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="273" - column="13"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#drmInitData`" - errorLine1=" this.drmInitData = drmInitData;" - errorLine2=" ~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="274" - column="13"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" this.timeMicros = timeMicros;" - errorLine2=" ~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="295" - column="13"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" this.position = position;" - errorLine2=" ~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="296" - column="13"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" return "[timeMicros=" + timeMicros + ", position=" + position + "]";" - errorLine2=" ~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="302" - column="66"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" return "[timeMicros=" + timeMicros + ", position=" + position + "]";" - errorLine2=" ~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="302" - column="37"/> - </issue> - - <issue - id="NewApi" - message="Class requires API level R (current min is 29): `android.media.MediaParser.SeekPoint`" - errorLine1=" SeekPoint other = (SeekPoint) obj;" - errorLine2=" ~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="313" - column="32"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" return timeMicros == other.timeMicros && position == other.position;" - errorLine2=" ~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="314" - column="54"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" return timeMicros == other.timeMicros && position == other.position;" - errorLine2=" ~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="314" - column="66"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" return timeMicros == other.timeMicros && position == other.position;" - errorLine2=" ~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="314" + file="frameworks/base/apex/media/framework/java/android/media/MediaTranscodingManager.java" + line="1651" column="20"/> </issue> <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" return timeMicros == other.timeMicros && position == other.position;" - errorLine2=" ~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="314" - column="34"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" int result = (int) timeMicros;" - errorLine2=" ~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="319" - column="32"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" result = 31 * result + (int) position;" - errorLine2=" ~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="320" - column="42"/> - </issue> - - <issue - id="NewApi" - message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`" - errorLine1=" public interface SeekableInputReader extends InputReader {" - errorLine2=" ~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="352" - column="50"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level 31 (current min is 29): `android.media.metrics.LogSessionId#LOG_SESSION_ID_NONE`" - errorLine1=" @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1071" - column="51"/> - </issue> - - <issue - id="NewApi" - message="Cast from `SeekableInputReader` to `InputReader` requires API level 30 (current min is 29)" - errorLine1=" mExoDataReader.mInputReader = seekableInputReader;" - errorLine2=" ~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1201" - column="39"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" mPendingSeekPosition = seekPoint.position;" - errorLine2=" ~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1287" - column="36"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" mPendingSeekTimeMicros = seekPoint.timeMicros;" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1288" - column="38"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" mExtractor.seek(seekPoint.position, seekPoint.timeMicros);" - errorLine2=" ~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1290" - column="29"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" mExtractor.seek(seekPoint.position, seekPoint.timeMicros);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + id="ParcelClassLoader" + message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead." + errorLine1=" Bundle out = parcel.readBundle(null);" + errorLine2=" ~~~~~~~~~~~~~~~~"> <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1290" - column="49"/> + file="frameworks/base/apex/media/framework/java/android/media/MediaSession2.java" + line="303" + column="33"/> </issue> <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.DrmInitData.SchemeInitData#uuid`" - errorLine1=" if (schemeInitData.uuid.equals(schemeUuid)) {" - errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + id="ParcelClassLoader" + message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead." + errorLine1=" mCustomExtras = in.readBundle();" + errorLine2=" ~~~~~~~~~~~~"> <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1579" - column="21"/> + file="frameworks/base/apex/media/framework/java/android/media/Session2Command.java" + line="104" + column="28"/> </issue> <issue - id="NewApi" - message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`" - errorLine1=" private static final class DataReaderAdapter implements InputReader {" - errorLine2=" ~~~~~~~~~~~"> + id="ParcelClassLoader" + message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead." + errorLine1=" mSessionLink = in.readParcelable(null);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1872" - column="61"/> + file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java" + line="141" + column="27"/> </issue> <issue - id="NewApi" - message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`" - errorLine1=" private static final class ParsableByteArrayAdapter implements InputReader {" - errorLine2=" ~~~~~~~~~~~"> + id="ParcelClassLoader" + message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead." + errorLine1=" Bundle extras = in.readBundle();" + errorLine2=" ~~~~~~~~~~~~"> <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1905" - column="68"/> + file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java" + line="144" + column="28"/> </issue> </issues> diff --git a/apex/media/service/Android.bp b/apex/media/service/Android.bp index cf384acccb12..834e5cbbd330 100644 --- a/apex/media/service/Android.bp +++ b/apex/media/service/Android.bp @@ -39,6 +39,7 @@ java_sdk_library { ":service-media-s-sources", ], libs: [ + "androidx.annotation_annotation", "updatable-media", "modules-annotation-minsdk", "modules-utils-build", @@ -46,6 +47,9 @@ java_sdk_library { jarjar_rules: "jarjar_rules.txt", sdk_version: "system_server_current", min_sdk_version: "29", // TODO: We may need to bump this at some point. + lint: { + strict_updatability_linting: true, + }, apex_available: [ "com.android.media", ], diff --git a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java index 7d47e250f99d..4223fa65fd53 100644 --- a/apex/media/service/java/com/android/server/media/MediaCommunicationService.java +++ b/apex/media/service/java/com/android/server/media/MediaCommunicationService.java @@ -46,6 +46,8 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.view.KeyEvent; +import androidx.annotation.RequiresApi; + import com.android.internal.annotations.GuardedBy; import com.android.modules.annotation.MinSdk; import com.android.server.SystemService; @@ -63,6 +65,7 @@ import java.util.concurrent.Executors; * @hide */ @MinSdk(Build.VERSION_CODES.S) +@RequiresApi(Build.VERSION_CODES.S) public class MediaCommunicationService extends SystemService { private static final String TAG = "MediaCommunicationSrv"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); diff --git a/apex/media/service/java/com/android/server/media/SessionPriorityList.java b/apex/media/service/java/com/android/server/media/SessionPriorityList.java index 47b14b63ddba..814586139123 100644 --- a/apex/media/service/java/com/android/server/media/SessionPriorityList.java +++ b/apex/media/service/java/com/android/server/media/SessionPriorityList.java @@ -18,9 +18,13 @@ package com.android.server.media; import android.annotation.Nullable; import android.media.Session2Token; +import android.os.Build; import android.util.Log; +import androidx.annotation.RequiresApi; + import com.android.internal.annotations.GuardedBy; +import com.android.modules.annotation.MinSdk; import com.android.server.media.MediaCommunicationService.Session2Record; import java.util.ArrayList; @@ -33,6 +37,8 @@ import java.util.List; * Higher priority session has more chance to be selected as media button session, * which receives the media button events. */ +@MinSdk(Build.VERSION_CODES.S) +@RequiresApi(Build.VERSION_CODES.S) class SessionPriorityList { private static final String TAG = "SessionPriorityList"; private final Object mLock = new Object(); diff --git a/apex/media/service/lint-baseline.xml b/apex/media/service/lint-baseline.xml index 05ce17c26872..def6baf0ff4f 100644 --- a/apex/media/service/lint-baseline.xml +++ b/apex/media/service/lint-baseline.xml @@ -1,37 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?> -<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.1.0-dev"> - - <issue - id="NewApi" - message="Call requires API level S (current min is 29): `MediaParceledListSlice`" - errorLine1=" new MediaParceledListSlice<>(getSession2TokensLocked(ALL.getIdentifier()));" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java" - line="242" - column="21"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level S (current min is 29): `MediaParceledListSlice`" - errorLine1=" userSession2Tokens = new MediaParceledListSlice<>(getSession2TokensLocked(userId));" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java" - line="243" - column="34"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level S (current min is 29): `MediaParceledListSlice`" - errorLine1=" MediaParceledListSlice parceledListSlice = new MediaParceledListSlice<>(result);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/service/java/com/android/server/media/MediaCommunicationService.java" - line="386" - column="60"/> - </issue> +<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev"> </issues> diff --git a/api/Android.bp b/api/Android.bp index 3075d385f1ca..d57f5dbdf948 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -116,7 +116,6 @@ combined_apis { "framework-graphics", "framework-media", "framework-mediaprovider", - "framework-nearby", "framework-permission", "framework-permission-s", "framework-scheduling", diff --git a/api/api.go b/api/api.go index aa9e399e7272..17649e80e81a 100644 --- a/api/api.go +++ b/api/api.go @@ -27,7 +27,6 @@ import ( const art = "art.module.public.api" const conscrypt = "conscrypt.module.public.api" const i18n = "i18n.module.public.api" -var modules_with_only_public_scope = []string{i18n, conscrypt} // The intention behind this soong plugin is to generate a number of "merged" // API-related modules that would otherwise require a large amount of very @@ -149,8 +148,6 @@ func createMergedStubsSrcjar(ctx android.LoadHookContext, modules []string) { // This produces the same annotations.zip as framework-doc-stubs, but by using // outputs from individual modules instead of all the source code. func createMergedAnnotations(ctx android.LoadHookContext, modules []string) { - // Conscrypt and i18n currently do not enable annotations - modules = removeAll(modules, []string{conscrypt, i18n}) props := genruleProps{} props.Name = proptools.StringPtr("sdk-annotations.zip") props.Tools = []string{"merge_annotation_zips", "soong_zip"} @@ -195,11 +192,8 @@ func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) { func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) { props := libraryProps{} - modules_with_system_stubs := removeAll(modules, modules_with_only_public_scope) props.Name = proptools.StringPtr("all-modules-system-stubs") - props.Static_libs = append( - transformArray(modules_with_only_public_scope, "", ".stubs"), - transformArray(modules_with_system_stubs, "", ".stubs.system")...) + props.Static_libs = transformArray(modules, "", ".stubs.system") props.Sdk_version = proptools.StringPtr("module_current") props.Visibility = []string{"//frameworks/base"} ctx.CreateModule(java.LibraryFactory, &props) @@ -226,8 +220,6 @@ func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []str func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) { var textFiles []MergedTxtDefinition - // Two module libraries currently do not support @SystemApi so only have the public scope. - bcpWithSystemApi := removeAll(bootclasspath, modules_with_only_public_scope) tagSuffix := []string{".api.txt}", ".removed-api.txt}"} for i, f := range []string{"current.txt", "removed.txt"} { @@ -241,14 +233,14 @@ func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_ textFiles = append(textFiles, MergedTxtDefinition{ TxtFilename: f, BaseTxt: ":non-updatable-system-" + f, - Modules: bcpWithSystemApi, + Modules: bootclasspath, ModuleTag: "{.system" + tagSuffix[i], Scope: "system", }) textFiles = append(textFiles, MergedTxtDefinition{ TxtFilename: f, BaseTxt: ":non-updatable-module-lib-" + f, - Modules: bcpWithSystemApi, + Modules: bootclasspath, ModuleTag: "{.module-lib" + tagSuffix[i], Scope: "module-lib", }) diff --git a/boot/Android.bp b/boot/Android.bp index 8958d70f63cf..55ffe7c3dda3 100644 --- a/boot/Android.bp +++ b/boot/Android.bp @@ -80,10 +80,6 @@ platform_bootclasspath { module: "com.android.mediaprovider-bootclasspath-fragment", }, { - apex: "com.android.nearby", - module: "com.android.nearby-bootclasspath-fragment", - }, - { apex: "com.android.os.statsd", module: "com.android.os.statsd-bootclasspath-fragment", }, diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp index dd33fdfc28c3..c8d2e0ec4aca 100644 --- a/cmds/incidentd/src/WorkDirectory.cpp +++ b/cmds/incidentd/src/WorkDirectory.cpp @@ -280,7 +280,7 @@ void ReportFile::addReport(const IncidentReportArgs& args) { // Lower privacy policy (less restrictive) wins. report->set_privacy_policy(args.getPrivacyPolicy()); } - report->set_all_sections(report->all_sections() | args.all()); + report->set_all_sections(report->all_sections() || args.all()); for (int section: args.sections()) { if (!has_section(*report, section)) { report->add_section(section); diff --git a/core/api/current.txt b/core/api/current.txt index 922ad16c2a6e..41e75909493d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -135,9 +135,6 @@ package android { field public static final String READ_HOME_APP_SEARCH_DATA = "android.permission.READ_HOME_APP_SEARCH_DATA"; field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE"; field public static final String READ_LOGS = "android.permission.READ_LOGS"; - field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO"; - field public static final String READ_MEDIA_IMAGE = "android.permission.READ_MEDIA_IMAGE"; - field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO"; field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY"; field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS"; field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; @@ -223,8 +220,6 @@ package android { field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES"; field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS"; field public static final String PHONE = "android.permission-group.PHONE"; - field public static final String READ_MEDIA_AURAL = "android.permission-group.READ_MEDIA_AURAL"; - field public static final String READ_MEDIA_VISUAL = "android.permission-group.READ_MEDIA_VISUAL"; field public static final String SENSORS = "android.permission-group.SENSORS"; field public static final String SMS = "android.permission-group.SMS"; field public static final String STORAGE = "android.permission-group.STORAGE"; @@ -734,6 +729,10 @@ package android { field public static final int freezesText = 16843116; // 0x101016c field public static final int fromAlpha = 16843210; // 0x10101ca field public static final int fromDegrees = 16843187; // 0x10101b3 + field public static final int fromExtendBottom; + field public static final int fromExtendLeft; + field public static final int fromExtendRight; + field public static final int fromExtendTop; field public static final int fromId = 16843850; // 0x101044a field public static final int fromScene = 16843741; // 0x10103dd field public static final int fromXDelta = 16843206; // 0x10101c6 @@ -1579,6 +1578,10 @@ package android { field public static final int titleTextStyle = 16843512; // 0x10102f8 field public static final int toAlpha = 16843211; // 0x10101cb field public static final int toDegrees = 16843188; // 0x10101b4 + field public static final int toExtendBottom; + field public static final int toExtendLeft; + field public static final int toExtendRight; + field public static final int toExtendTop; field public static final int toId = 16843849; // 0x1010449 field public static final int toScene = 16843742; // 0x10103de field public static final int toXDelta = 16843207; // 0x10101c7 @@ -7311,10 +7314,10 @@ package android.app.admin { method @Nullable public java.util.List<java.lang.String> getDelegatePackages(@NonNull android.content.ComponentName, @NonNull String); method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String); method public CharSequence getDeviceOwnerLockScreenInfo(); - method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); - method @NonNull public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); - method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); - method @NonNull public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @Nullable public android.graphics.drawable.Drawable getDrawable(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); + method @Nullable public android.graphics.drawable.Drawable getDrawableForDensity(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.concurrent.Callable<android.graphics.drawable.Drawable>); method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName); method @NonNull public String getEnrollmentSpecificId(); method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName); @@ -12225,7 +12228,7 @@ package android.content.pm { field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR; field public static final int TYPE_BUILTIN = 0; // 0x0 field public static final int TYPE_DYNAMIC = 1; // 0x1 - field public static final int TYPE_SDK = 3; // 0x3 + field public static final int TYPE_SDK_PACKAGE = 3; // 0x3 field public static final int TYPE_STATIC = 2; // 0x2 field public static final int VERSION_UNDEFINED = -1; // 0xffffffff } @@ -12953,6 +12956,10 @@ package android.database { field @NonNull public static final android.os.Parcelable.Creator<android.database.CursorWindow> CREATOR; } + public class CursorWindowAllocationException extends java.lang.RuntimeException { + ctor public CursorWindowAllocationException(@NonNull String); + } + public class CursorWrapper implements android.database.Cursor { ctor public CursorWrapper(android.database.Cursor); method public void close(); @@ -13984,6 +13991,7 @@ package android.graphics { enum_constant @Deprecated public static final android.graphics.Bitmap.Config ARGB_4444; enum_constant public static final android.graphics.Bitmap.Config ARGB_8888; enum_constant public static final android.graphics.Bitmap.Config HARDWARE; + enum_constant public static final android.graphics.Bitmap.Config RGBA_1010102; enum_constant public static final android.graphics.Bitmap.Config RGBA_F16; enum_constant public static final android.graphics.Bitmap.Config RGB_565; } @@ -22723,6 +22731,7 @@ package android.media { method public int describeContents(); method @Nullable public String getClientPackageName(); method public int getConnectionState(); + method @NonNull public java.util.Set<java.lang.String> getDeduplicationIds(); method @Nullable public CharSequence getDescription(); method @Nullable public android.os.Bundle getExtras(); method @NonNull public java.util.List<java.lang.String> getFeatures(); @@ -22756,6 +22765,7 @@ package android.media { method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures(); method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String); method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int); + method @NonNull public android.media.MediaRoute2Info.Builder setDeduplicationIds(@NonNull java.util.Set<java.lang.String>); method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence); method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle); method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri); @@ -23282,8 +23292,12 @@ package android.media { public final class RouteDiscoveryPreference implements android.os.Parcelable { method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getAllowedPackages(); + method @NonNull public java.util.List<java.lang.String> getDeduplicationPackageOrder(); method @NonNull public java.util.List<java.lang.String> getPreferredFeatures(); + method @NonNull public java.util.List<java.lang.String> getRequiredFeatures(); method public boolean shouldPerformActiveScan(); + method public boolean shouldRemoveDuplicates(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR; } @@ -23292,7 +23306,10 @@ package android.media { ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean); ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference); method @NonNull public android.media.RouteDiscoveryPreference build(); + method @NonNull public android.media.RouteDiscoveryPreference.Builder setAllowedPackages(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.media.RouteDiscoveryPreference.Builder setDeduplicationPackageOrder(@NonNull java.util.List<java.lang.String>); method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.media.RouteDiscoveryPreference.Builder setRequiredFeatures(@NonNull java.util.List<java.lang.String>); method @NonNull public android.media.RouteDiscoveryPreference.Builder setShouldPerformActiveScan(boolean); } @@ -31408,6 +31425,9 @@ package android.os { method @Nullable public byte[] createByteArray(); method @Nullable public char[] createCharArray(); method @Nullable public double[] createDoubleArray(); + method @Nullable public <T> T createFixedArray(@NonNull Class<T>, @NonNull int...); + method @Nullable public <T, S extends android.os.IInterface> T createFixedArray(@NonNull Class<T>, @NonNull java.util.function.Function<android.os.IBinder,S>, @NonNull int...); + method @Nullable public <T, S extends android.os.Parcelable> T createFixedArray(@NonNull Class<T>, @NonNull android.os.Parcelable.Creator<S>, @NonNull int...); method @Nullable public float[] createFloatArray(); method @Nullable public int[] createIntArray(); method @Nullable public <T extends android.os.IInterface> T[] createInterfaceArray(@NonNull java.util.function.IntFunction<T[]>, @NonNull java.util.function.Function<android.os.IBinder,T>); @@ -31449,6 +31469,9 @@ package android.os { method public void readException(); method public void readException(int, String); method public android.os.ParcelFileDescriptor readFileDescriptor(); + method public <T> void readFixedArray(@NonNull T); + method public <T, S extends android.os.IInterface> void readFixedArray(@NonNull T, @NonNull java.util.function.Function<android.os.IBinder,S>); + method public <T, S extends android.os.Parcelable> void readFixedArray(@NonNull T, @NonNull android.os.Parcelable.Creator<S>); method public float readFloat(); method public void readFloatArray(@NonNull float[]); method @Deprecated @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader); @@ -31509,6 +31532,7 @@ package android.os { method public void writeDoubleArray(@Nullable double[]); method public void writeException(@NonNull Exception); method public void writeFileDescriptor(@NonNull java.io.FileDescriptor); + method public <T> void writeFixedArray(@Nullable T, int, @NonNull int...); method public void writeFloat(float); method public void writeFloatArray(@Nullable float[]); method public void writeInt(int); @@ -31729,9 +31753,14 @@ package android.os { method public void release(); method public void release(int); method public void setReferenceCounted(boolean); + method public void setStateListener(@NonNull java.util.concurrent.Executor, @Nullable android.os.PowerManager.WakeLockStateListener); method public void setWorkSource(android.os.WorkSource); } + public static interface PowerManager.WakeLockStateListener { + method public void onStateChanged(boolean); + } + public class Process { ctor public Process(); method public static final long getElapsedCpuTime(); @@ -37882,6 +37911,7 @@ package android.service.autofill { method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback); method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback); method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback); + field public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE"; field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final String SERVICE_META_DATA = "android.autofill"; } @@ -38037,6 +38067,7 @@ package android.service.autofill { public final class FillRequest implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.os.Bundle getClientState(); + method @Nullable public android.content.IntentSender getDelayedFillIntentSender(); method @NonNull public java.util.List<android.service.autofill.FillContext> getFillContexts(); method public int getFlags(); method public int getId(); @@ -38051,6 +38082,7 @@ package android.service.autofill { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR; + field public static final int FLAG_DELAY_FILL = 4; // 0x4 field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2 field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1 } @@ -41196,6 +41228,7 @@ package android.telephony { field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int"; field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int"; field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool"; + field public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL = "is_opportunistic_subscription_bool"; field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool"; field public static final String KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "lte_rsrq_thresholds_int_array"; field public static final String KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "lte_rssnr_thresholds_int_array"; @@ -41280,6 +41313,7 @@ package android.telephony { field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; + field public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING = "subscription_group_uuid_string"; field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool"; field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool"; field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL = "supports_device_to_device_communication_using_rtp_bool"; @@ -41323,6 +41357,7 @@ package android.telephony { field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string"; field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool"; field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool"; + field public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000"; field public static final int SERVICE_CLASS_NONE = 0; // 0x0 field public static final int SERVICE_CLASS_VOICE = 1; // 0x1 field public static final int USSD_OVER_CS_ONLY = 2; // 0x2 @@ -51643,6 +51678,7 @@ package android.view.accessibility { method public boolean isSelected(); method public boolean isShowingHintText(); method public boolean isTextEntryKey(); + method public boolean isTextSelectable(); method public boolean isVisibleToUser(); method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View); method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int); @@ -51706,6 +51742,7 @@ package android.view.accessibility { method public void setStateDescription(@Nullable CharSequence); method public void setText(CharSequence); method public void setTextEntryKey(boolean); + method public void setTextSelectable(boolean); method public void setTextSelection(int, int); method public void setTooltipText(@Nullable CharSequence); method public void setTouchDelegateInfo(@NonNull android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 8ea1abca7a3f..6cde547e6571 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -54,6 +54,21 @@ package android.app { method public void onCanceled(@NonNull android.app.PendingIntent); } + public class PropertyInvalidatedCache<Query, Result> { + ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>); + method public final void disableForCurrentProcess(); + method public final void invalidateCache(); + method public static void invalidateCache(int, @NonNull String); + method @Nullable public final Result query(@NonNull Query); + field public static final int MODULE_BLUETOOTH = 2; // 0x2 + } + + public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> { + ctor public PropertyInvalidatedCache.QueryHandler(); + method @Nullable public abstract R apply(@NonNull Q); + method public boolean shouldBypassCache(@NonNull Q); + } + public class StatusBarManager { method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean); } @@ -446,6 +461,17 @@ package android.net { } +package android.net.netstats { + + public class NetworkStatsDataMigrationUtils { + method @NonNull public static android.net.NetworkStatsCollection readPlatformCollection(@NonNull String, long) throws java.io.IOException; + field public static final String PREFIX_UID = "uid"; + field public static final String PREFIX_UID_TAG = "uid_tag"; + field public static final String PREFIX_XT = "xt"; + } + +} + package android.os { public final class BatteryStatsManager { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 171ee9a0a95b..83e472d6128d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -146,6 +146,7 @@ package android { field public static final String KILL_UID = "android.permission.KILL_UID"; field public static final String LAUNCH_DEVICE_MANAGER_SETUP = "android.permission.LAUNCH_DEVICE_MANAGER_SETUP"; field public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS"; + field public static final String LOCATION_BYPASS = "android.permission.LOCATION_BYPASS"; field public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE"; field public static final String LOOP_RADIO = "android.permission.LOOP_RADIO"; field public static final String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY"; @@ -165,6 +166,7 @@ package android { field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING"; field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; + field public static final String MANAGE_GAME_ACTIVITY = "android.permission.MANAGE_GAME_ACTIVITY"; field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE"; field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION"; field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; @@ -201,6 +203,7 @@ package android { field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS"; field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE"; field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE"; + field public static final String MODIFY_TOUCH_MODE_STATE = "android.permission.MODIFY_TOUCH_MODE_STATE"; field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE"; field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE"; field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING"; @@ -258,6 +261,7 @@ package android { field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL"; field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL"; field public static final String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS"; + field public static final String RECEIVE_BLUETOOTH_MAP = "android.permission.RECEIVE_BLUETOOTH_MAP"; field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"; 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"; @@ -302,6 +306,7 @@ package android { field public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED"; field public static final String SET_SCREEN_COMPATIBILITY = "android.permission.SET_SCREEN_COMPATIBILITY"; field public static final String SET_SYSTEM_AUDIO_CAPTION = "android.permission.SET_SYSTEM_AUDIO_CAPTION"; + field public static final String SET_UNRESTRICTED_KEEP_CLEAR_AREAS = "android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS"; field public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"; field public static final String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT"; field public static final String SET_WALLPAPER_DIM_AMOUNT = "android.permission.SET_WALLPAPER_DIM_AMOUNT"; @@ -348,6 +353,7 @@ package android { field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final String WRITE_OBB = "android.permission.WRITE_OBB"; + field public static final String WRITE_SMS = "android.permission.WRITE_SMS"; } public static final class Manifest.permission_group { @@ -1057,8 +1063,8 @@ package android.app.admin { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException; method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException; - method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>); - method @NonNull public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...); + method @Nullable public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>); + method @Nullable public String getString(@NonNull String, @NonNull java.util.concurrent.Callable<java.lang.String>, @NonNull java.lang.Object...); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public int getUserProvisioningState(); method public boolean isDeviceManaged(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned(); @@ -1182,6 +1188,15 @@ package android.app.admin { field public static final String WORK_PROFILE_PAUSED_TITLE = "MediaProvider.WORK_PROFILE_PAUSED_TITLE"; } + public static final class DevicePolicyResources.Strings.PermissionController { + field public static final String BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE = "PermissionController.BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE"; + field public static final String BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE = "PermissionController.BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE"; + field public static final String FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE = "PermissionController.FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE"; + field public static final String HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE = "PermissionController.HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE"; + field public static final String LOCATION_AUTO_GRANTED_MESSAGE = "PermissionController.LOCATION_AUTO_GRANTED_MESSAGE"; + field public static final String WORK_PROFILE_DEFAULT_APPS_TITLE = "PermissionController.WORK_PROFILE_DEFAULT_APPS_TITLE"; + } + public final class DevicePolicyStringResource implements android.os.Parcelable { ctor public DevicePolicyStringResource(@NonNull android.content.Context, @NonNull String, @StringRes int); method public int describeContents(); @@ -1194,6 +1209,7 @@ package android.app.admin { public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable { method public boolean canDeviceOwnerGrantSensorsPermissions(); method public int describeContents(); + method @NonNull public android.os.PersistableBundle getAdminExtras(); method @NonNull public android.content.ComponentName getDeviceAdminComponentName(); method public long getLocalTime(); method @Nullable public java.util.Locale getLocale(); @@ -1207,6 +1223,7 @@ package android.app.admin { public static final class FullyManagedDeviceProvisioningParams.Builder { ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String); method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build(); + method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setAdminExtras(@NonNull android.os.PersistableBundle); method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setCanDeviceOwnerGrantSensorsPermissions(boolean); method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean); method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long); @@ -1217,6 +1234,7 @@ package android.app.admin { public final class ManagedProfileProvisioningParams implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.accounts.Account getAccountToMigrate(); + method @NonNull public android.os.PersistableBundle getAdminExtras(); method @NonNull public String getOwnerName(); method @NonNull public android.content.ComponentName getProfileAdminComponentName(); method @Nullable public String getProfileName(); @@ -1231,6 +1249,7 @@ package android.app.admin { ctor public ManagedProfileProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String); method @NonNull public android.app.admin.ManagedProfileProvisioningParams build(); method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAccountToMigrate(@Nullable android.accounts.Account); + method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAdminExtras(@NonNull android.os.PersistableBundle); method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setKeepingAccountOnMigration(boolean); method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean); method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setOrganizationOwnedProvisioning(boolean); @@ -2610,6 +2629,8 @@ package android.companion.virtual { public final class VirtualDeviceParams implements android.os.Parcelable { method public int describeContents(); + method @Nullable public java.util.Set<android.content.ComponentName> getAllowedActivities(); + method @Nullable public java.util.Set<android.content.ComponentName> getBlockedActivities(); method public int getLockState(); method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -2621,6 +2642,8 @@ package android.companion.virtual { public static final class VirtualDeviceParams.Builder { ctor public VirtualDeviceParams.Builder(); method @NonNull public android.companion.virtual.VirtualDeviceParams build(); + method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@Nullable java.util.Set<android.content.ComponentName>); + method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@Nullable java.util.Set<android.content.ComponentName>); method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>); } @@ -2678,6 +2701,7 @@ package android.content { method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int); method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle); + method public void sendBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[], @Nullable android.app.BroadcastOptions); method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle); field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context"; @@ -2688,7 +2712,7 @@ package android.content { field public static final String BATTERY_STATS_SERVICE = "batterystats"; field @Deprecated public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000 field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000 - field public static final String CLOUDSEARCH_SERVICE = "cloudsearch_service"; + field public static final String CLOUDSEARCH_SERVICE = "cloudsearch"; field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions"; field public static final String CONTEXTHUB_SERVICE = "contexthub"; field public static final String ETHERNET_SERVICE = "ethernet"; @@ -2781,6 +2805,7 @@ package android.content { field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP"; field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED"; field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED"; + field public static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED"; field @RequiresPermission(android.Manifest.permission.START_VIEW_APP_FEATURES) public static final String ACTION_VIEW_APP_FEATURES = "android.intent.action.VIEW_APP_FEATURES"; field public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST"; field public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS"; @@ -2803,7 +2828,9 @@ package android.content { field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME"; field public static final String EXTRA_SHOWING_ATTRIBUTION = "android.intent.extra.SHOWING_ATTRIBUTION"; field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP"; + field public static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle"; field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE"; + field public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 16777216; // 0x1000000 field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000 field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION"; } @@ -3686,6 +3713,7 @@ package android.hardware.hdmi { method public void sendKeyEvent(int, boolean); method public void sendVendorCommand(int, byte[], boolean); method public void setVendorCommandListener(@NonNull android.hardware.hdmi.HdmiControlManager.VendorCommandListener); + method public void setVendorCommandListener(@NonNull android.hardware.hdmi.HdmiControlManager.VendorCommandListener, int); } public static interface HdmiClient.OnDeviceSelectedListener { @@ -5534,9 +5562,9 @@ package android.location { ctor public LastLocationRequest.Builder(); ctor public LastLocationRequest.Builder(@NonNull android.location.LastLocationRequest); method @NonNull public android.location.LastLocationRequest build(); - method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LastLocationRequest.Builder setAdasGnssBypass(boolean); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LastLocationRequest.Builder setAdasGnssBypass(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) public android.location.LastLocationRequest.Builder setHiddenFromAppOps(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LastLocationRequest.Builder setLocationSettingsIgnored(boolean); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LastLocationRequest.Builder setLocationSettingsIgnored(boolean); } public class Location implements android.os.Parcelable { @@ -5565,7 +5593,7 @@ package android.location { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.app.PendingIntent); - method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAdasGnssLocationEnabled(boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public void setAdasGnssLocationEnabled(boolean); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackage(@Nullable String); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void setExtraLocationControllerPackageEnabled(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, @NonNull android.os.UserHandle); @@ -5598,7 +5626,7 @@ package android.location { method @Deprecated @NonNull public android.location.LocationRequest setFastestInterval(long); method @Deprecated public void setHideFromAppOps(boolean); method @Deprecated @NonNull public android.location.LocationRequest setInterval(long); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest setLocationSettingsIgnored(boolean); + method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest setLocationSettingsIgnored(boolean); method @Deprecated @NonNull public android.location.LocationRequest setLowPowerMode(boolean); method @Deprecated @NonNull public android.location.LocationRequest setNumUpdates(int); method @Deprecated @NonNull public android.location.LocationRequest setProvider(@NonNull String); @@ -5614,9 +5642,9 @@ package android.location { } public static final class LocationRequest.Builder { - method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest.Builder setAdasGnssBypass(boolean); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest.Builder setAdasGnssBypass(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS) public android.location.LocationRequest.Builder setHiddenFromAppOps(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.location.LocationRequest.Builder setLocationSettingsIgnored(boolean); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.LOCATION_BYPASS}) public android.location.LocationRequest.Builder setLocationSettingsIgnored(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public android.location.LocationRequest.Builder setLowPower(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource); } @@ -5868,6 +5896,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback); method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback); + field public static final String ACTION_VOLUME_CHANGED = "android.media.VOLUME_CHANGED_ACTION"; field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1 field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4 field public static final int AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS = 2; // 0x2 @@ -5876,7 +5905,11 @@ package android.media { field public static final int DEVICE_VOLUME_BEHAVIOR_FIXED = 2; // 0x2 field public static final int DEVICE_VOLUME_BEHAVIOR_FULL = 1; // 0x1 field public static final int DEVICE_VOLUME_BEHAVIOR_VARIABLE = 0; // 0x0 + field public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"; + field public static final String EXTRA_VOLUME_STREAM_VALUE = "android.media.EXTRA_VOLUME_STREAM_VALUE"; + field public static final int FLAG_BLUETOOTH_ABS_VOLUME = 64; // 0x40 field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int STREAM_ASSISTANT = 11; // 0xb + field public static final int STREAM_BLUETOOTH_SCO = 6; // 0x6 field public static final int SUCCESS = 0; // 0x0 } @@ -8918,6 +8951,8 @@ package android.os { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanResults(@NonNull android.os.WorkSource, int); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStarted(@NonNull android.os.WorkSource, boolean); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStopped(@NonNull android.os.WorkSource, boolean); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockReleasedFromSource(@NonNull android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportMobileRadioPowerState(boolean, int); @@ -10219,6 +10254,7 @@ package android.provider { method @Deprecated public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean); method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, @Nullable String, boolean); field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS"; + field public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS"; field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS"; field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS"; field public static final String ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS = "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS"; @@ -10259,6 +10295,8 @@ package android.provider { } public static final class Settings.Secure extends android.provider.Settings.NameValueTable { + method public static int getIntForUser(@NonNull android.content.ContentResolver, @NonNull String, int, int); + method @Nullable public static String getStringForUser(@NonNull android.content.ContentResolver, @NonNull String, int); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, @Nullable String, boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String); field @Deprecated public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED = "accessibility_display_magnification_navbar_enabled"; @@ -10610,6 +10648,8 @@ package android.service.attention { method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onCancelAttentionCheck(@NonNull android.service.attention.AttentionService.AttentionCallback); method public abstract void onCheckAttention(@NonNull android.service.attention.AttentionService.AttentionCallback); + method public void onStartProximityUpdates(@NonNull android.service.attention.AttentionService.ProximityCallback); + method public void onStopProximityUpdates(); field public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6; // 0x6 field public static final int ATTENTION_FAILURE_CANCELLED = 3; // 0x3 field public static final int ATTENTION_FAILURE_PREEMPTED = 4; // 0x4 @@ -10617,6 +10657,7 @@ package android.service.attention { field public static final int ATTENTION_FAILURE_UNKNOWN = 2; // 0x2 field public static final int ATTENTION_SUCCESS_ABSENT = 0; // 0x0 field public static final int ATTENTION_SUCCESS_PRESENT = 1; // 0x1 + field public static final double PROXIMITY_UNKNOWN = -1.0; field public static final String SERVICE_INTERFACE = "android.service.attention.AttentionService"; } @@ -10625,6 +10666,10 @@ package android.service.attention { method public void onSuccess(int, long); } + public static final class AttentionService.ProximityCallback { + method public void onProximityUpdate(double); + } + } package android.service.autofill { @@ -11034,7 +11079,7 @@ package android.service.games { public class GameService extends android.app.Service { ctor public GameService(); - method public final void createGameSession(@IntRange(from=0) int); + method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final void createGameSession(@IntRange(from=0) int); method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent); method public void onConnected(); method public void onDisconnected(); @@ -11048,7 +11093,8 @@ package android.service.games { method public void onCreate(); method public void onDestroy(); method public void onGameTaskFocusChanged(boolean); - method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public final boolean restartGame(); + method public void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean); + method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final boolean restartGame(); method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams); method public void takeScreenshot(@NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSession.ScreenshotCallback); } @@ -11843,6 +11889,7 @@ package android.telecom { public abstract class ConnectionService extends android.app.Service { method public final void addExistingConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.Connection, @NonNull android.telecom.Conference); + method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telecom.Connection onCreateUnknownConnection(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.telecom.ConnectionRequest); } public abstract class InCallService extends android.app.Service { diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index dbb427467a68..e17a9bb1512c 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -294,7 +294,6 @@ SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit ServiceName: android.content.Context#CLOUDSEARCH_SERVICE: - Inconsistent service value; expected `cloudsearch`, was `cloudsearch_service` (Note: Do not change the name of already released services, which will break tools using `adb shell dumpsys`. Instead add `@SuppressLint("ServiceName"))` UserHandleName: android.app.search.SearchAction.Builder#setUserHandle(android.os.UserHandle): diff --git a/core/api/test-current.txt b/core/api/test-current.txt index bd7b084615d1..52a180b3c9e0 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -105,6 +105,7 @@ package android.app { @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.OnBackInvokedDispatcherOwner android.view.View.OnCreateContextMenuListener android.view.Window.Callback { method public final boolean addDumpable(@NonNull android.util.Dumpable); + method public void dumpInternal(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[]); method public void onMovedToDisplay(int, android.content.res.Configuration); } @@ -354,6 +355,32 @@ package android.app { ctor public PictureInPictureUiState(boolean); } + public class PropertyInvalidatedCache<Query, Result> { + ctor public PropertyInvalidatedCache(int, int, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>); + method @NonNull public static String createPropertyName(int, @NonNull String); + method public final void disableForCurrentProcess(); + method public static void disableForTestMode(); + method public final void disableInstance(); + method public final void disableSystemWide(); + method public final void forgetDisableLocal(); + method public boolean getDisabledState(); + method public final void invalidateCache(); + method public static void invalidateCache(int, @NonNull String); + method public final boolean isDisabled(); + method @Nullable public final Result query(@NonNull Query); + method public static void setTestMode(boolean); + method public void testPropertyName(); + field public static final int MODULE_BLUETOOTH = 2; // 0x2 + field public static final int MODULE_SYSTEM = 1; // 0x1 + field public static final int MODULE_TEST = 0; // 0x0 + } + + public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> { + ctor public PropertyInvalidatedCache.QueryHandler(); + method @Nullable public abstract R apply(@NonNull Q); + method public boolean shouldBypassCache(@NonNull Q); + } + public class StatusBarManager { method public void cancelRequestAddTile(@NonNull String); method public void clickNotification(@Nullable String, int, int, boolean); @@ -461,6 +488,7 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs(); method public void forceUpdateUserSetupComplete(int); method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages(); + method public int getDeviceOwnerType(@NonNull android.content.ComponentName); method @NonNull public java.util.Set<java.lang.String> getDisallowedSystemApps(@NonNull android.content.ComponentName, int, @NonNull String); method public long getLastBugReportRequestTime(); method public long getLastNetworkLogRetrievalTime(); @@ -476,6 +504,7 @@ package android.app.admin { method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int); + method public void setDeviceOwnerType(@NonNull android.content.ComponentName, int); method @RequiresPermission(android.Manifest.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 String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED"; @@ -1091,7 +1120,7 @@ package android.hardware.camera2 { package android.hardware.devicestate { public final class DeviceStateManager { - method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest); + method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest(); method @NonNull public int[] getSupportedStates(); method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback); method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback); diff --git a/core/java/Android.bp b/core/java/Android.bp index 8234f03100ca..5649c5f1f7db 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -128,6 +128,7 @@ filegroup { "android/os/IThermalStatusListener.aidl", "android/os/IThermalService.aidl", "android/os/IPowerManager.aidl", + "android/os/IWakeLockCallback.aidl", ], } diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java index bb2b8d45fd61..735df805b872 100644 --- a/core/java/android/accessibilityservice/TouchInteractionController.java +++ b/core/java/android/accessibilityservice/TouchInteractionController.java @@ -376,7 +376,7 @@ public final class TouchInteractionController { throw new IllegalStateException( "State transitions are not allowed without first adding a callback."); } - if (mState != STATE_TOUCH_INTERACTING) { + if (mState != STATE_TOUCH_INTERACTING && mState != STATE_DRAGGING) { throw new IllegalStateException( "State transitions are not allowed in " + stateToString(mState)); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index a626cdaed4bc..530666b8f1d7 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -33,12 +33,16 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.StyleRes; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UiContext; import android.app.VoiceInteractor.Request; import android.app.admin.DevicePolicyManager; import android.app.assist.AssistContent; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentCallbacks2; import android.content.ComponentName; @@ -788,6 +792,16 @@ public class Activity extends ContextThemeWrapper private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064; private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065; + /** + * After {@link Build.VERSION_CODES#TIRAMISU}, + * {@link #dump(String, FileDescriptor, PrintWriter, String[])} is not called if + * {@code dumpsys activity} is called with some special arguments. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + @VisibleForTesting + private static final long DUMP_IGNORES_SPECIAL_ARGS = 149254050L; + private static class ManagedDialog { Dialog mDialog; Bundle mArgs; @@ -7102,7 +7116,18 @@ public class Activity extends ContextThemeWrapper /** * Print the Activity's state into the given stream. This gets invoked if - * you run "adb shell dumpsys activity <activity_component_name>". + * you run <code>adb shell dumpsys activity <activity_component_name></code>. + * + * <p>This method won't be called if the app targets + * {@link android.os.Build.VERSION_CODES#TIRAMISU} or later if the dump request starts with one + * of the following arguments: + * <ul> + * <li>--autofill + * <li>--contentcapture + * <li>--translation + * <li>--list-dumpables + * <li>--dump-dumpable + * </ul> * * @param prefix Desired prefix to prepend at each line of output. * @param fd The raw file descriptor that the dump is being sent to. @@ -7129,11 +7154,20 @@ public class Activity extends ContextThemeWrapper return mDumpableContainer.addDumpable(dumpable); } - void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd, + /** + * This is the real method called by {@code ActivityThread}, but it's also exposed so + * CTS can test for the special args cases. + * + * @hide + */ + @TestApi + @VisibleForTesting + @SuppressLint("OnNameExpected") + public void dumpInternal(@NonNull String prefix, + @SuppressLint("UseParcelFileDescriptor") @Nullable FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { - String innerPrefix = prefix + " "; - - if (args != null && args.length > 0) { + if (args != null && args.length > 0 + && CompatChanges.isChangeEnabled(DUMP_IGNORES_SPECIAL_ARGS)) { // Handle special cases switch (args[0]) { case "--autofill": @@ -7168,6 +7202,12 @@ public class Activity extends ContextThemeWrapper return; } } + dump(prefix, fd, writer, args); + } + + void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd, + @NonNull PrintWriter writer, @Nullable String[] args) { + String innerPrefix = prefix + " "; writer.print(prefix); writer.print("Local Activity "); writer.print(Integer.toHexString(System.identityHashCode(this))); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index cce7dd338b3d..a58ceaa99022 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -215,6 +215,14 @@ public abstract class ActivityManagerInternal { public abstract boolean isSystemReady(); /** + * Returns package name given pid. + * + * @param pid The pid we are searching package name for. + */ + @Nullable + public abstract String getPackageNameByPid(int pid); + + /** * Sets if the given pid has an overlay UI or not. * * @param pid The pid we are setting overlay UI for. diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3825d8d3177b..3289304a0df4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -200,6 +200,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; +import com.android.internal.os.BinderCallsStats; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.os.SomeArgs; @@ -1004,6 +1005,11 @@ public final class ActivityThread extends ClientTransactionHandler RemoteCallback finishCallback; } + static final class DumpResourcesData { + public ParcelFileDescriptor fd; + public RemoteCallback finishCallback; + } + static final class UpdateCompatibilityData { String pkg; CompatibilityInfo info; @@ -1315,6 +1321,20 @@ public final class ActivityThread extends ClientTransactionHandler sendMessage(H.SCHEDULE_CRASH, args, typeId); } + @Override + public void dumpResources(ParcelFileDescriptor fd, RemoteCallback callback) { + DumpResourcesData data = new DumpResourcesData(); + try { + data.fd = fd.dup(); + data.finishCallback = callback; + sendMessage(H.DUMP_RESOURCES, data, 0, 0, false /*async*/); + } catch (IOException e) { + Slog.w(TAG, "dumpResources failed", e); + } finally { + IoUtils.closeQuietly(fd); + } + } + public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken, String prefix, String[] args) { DumpComponentInfo data = new DumpComponentInfo(); @@ -2038,6 +2058,7 @@ public final class ActivityThread extends ClientTransactionHandler public static final int UPDATE_UI_TRANSLATION_STATE = 163; public static final int SET_CONTENT_CAPTURE_OPTIONS_CALLBACK = 164; public static final int DUMP_GFXINFO = 165; + public static final int DUMP_RESOURCES = 166; public static final int INSTRUMENT_WITHOUT_RESTART = 170; public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171; @@ -2091,6 +2112,7 @@ public final class ActivityThread extends ClientTransactionHandler case INSTRUMENT_WITHOUT_RESTART: return "INSTRUMENT_WITHOUT_RESTART"; case FINISH_INSTRUMENTATION_WITHOUT_RESTART: return "FINISH_INSTRUMENTATION_WITHOUT_RESTART"; + case DUMP_RESOURCES: return "DUMP_RESOURCES"; } } return Integer.toString(code); @@ -2206,6 +2228,9 @@ public final class ActivityThread extends ClientTransactionHandler case DUMP_HEAP: handleDumpHeap((DumpHeapData) msg.obj); break; + case DUMP_RESOURCES: + handleDumpResources((DumpResourcesData) msg.obj); + break; case DUMP_ACTIVITY: handleDumpActivity((DumpComponentInfo)msg.obj); break; @@ -4584,6 +4609,23 @@ public final class ActivityThread extends ClientTransactionHandler } } + private void handleDumpResources(DumpResourcesData info) { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + PrintWriter pw = new FastPrintWriter(new FileOutputStream( + info.fd.getFileDescriptor())); + + Resources.dumpHistory(pw, ""); + pw.flush(); + if (info.finishCallback != null) { + info.finishCallback.sendResult(null); + } + } finally { + IoUtils.closeQuietly(info.fd); + StrictMode.setThreadPolicy(oldPolicy); + } + } + private void handleDumpActivity(DumpComponentInfo info) { final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { @@ -4591,7 +4633,7 @@ public final class ActivityThread extends ClientTransactionHandler if (r != null && r.activity != null) { PrintWriter pw = new FastPrintWriter(new FileOutputStream( info.fd.getFileDescriptor())); - r.activity.dump(info.prefix, info.fd.getFileDescriptor(), pw, info.args); + r.activity.dumpInternal(info.prefix, info.fd.getFileDescriptor(), pw, info.args); pw.flush(); } } finally { @@ -7812,6 +7854,8 @@ public final class ActivityThread extends ClientTransactionHandler MediaFrameworkPlatformInitializer.setMediaServiceManager(new MediaServiceManager()); MediaFrameworkInitializer.setMediaServiceManager(new MediaServiceManager()); BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager()); + BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> { + BinderCallsStats.startForBluetooth(context); }); } private void purgePendingResources() { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 0d1bc05df67b..fdf37f6633ee 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2363,11 +2363,11 @@ public class AppOpsManager { Manifest.permission.USE_BIOMETRIC, Manifest.permission.ACTIVITY_RECOGNITION, Manifest.permission.SMS_FINANCIAL_TRANSACTIONS, - Manifest.permission.READ_MEDIA_AUDIO, + null, null, // no permission for OP_WRITE_MEDIA_AUDIO - Manifest.permission.READ_MEDIA_VIDEO, + null, null, // no permission for OP_WRITE_MEDIA_VIDEO - Manifest.permission.READ_MEDIA_IMAGE, + null, null, // no permission for OP_WRITE_MEDIA_IMAGES null, // no permission for OP_LEGACY_STORAGE null, // no permission for OP_ACCESS_ACCESSIBILITY diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 7b55b6c0e458..b1956effb093 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -16,6 +16,11 @@ package android.app; +import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; +import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_NOT_COLORED; +import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED; +import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; +import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON_BADGE; import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256; import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512; import static android.content.pm.Checksum.TYPE_WHOLE_MD5; @@ -31,6 +36,7 @@ import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.UserIdInt; import android.annotation.XmlRes; +import android.app.admin.DevicePolicyManager; import android.app.role.RoleManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -62,7 +68,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; @@ -74,7 +79,6 @@ import android.content.pm.SuspendDialogInfo; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; import android.content.pm.dex.ArtManager; -import android.content.pm.pkg.FrameworkPackageUserState; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.XmlResourceParser; @@ -122,7 +126,6 @@ import dalvik.system.VMRuntime; import libcore.util.EmptyArray; -import java.io.File; import java.lang.ref.WeakReference; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; @@ -172,6 +175,8 @@ public class ApplicationPackageManager extends PackageManager { private PackageInstaller mInstaller; @GuardedBy("mLock") private ArtManager mArtManager; + @GuardedBy("mLock") + private DevicePolicyManager mDevicePolicyManager; @GuardedBy("mDelegates") private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>(); @@ -188,6 +193,15 @@ public class ApplicationPackageManager extends PackageManager { } } + DevicePolicyManager getDevicePolicyManager() { + synchronized (mLock) { + if (mDevicePolicyManager == null) { + mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class); + } + return mDevicePolicyManager; + } + } + private PermissionManager getPermissionManager() { synchronized (mLock) { if (mPermissionManager == null) { @@ -1876,12 +1890,27 @@ public class ApplicationPackageManager extends PackageManager { if (!hasUserBadge(user.getIdentifier())) { return icon; } + + final Drawable badgeForeground = getDevicePolicyManager().getDrawable( + getUpdatableUserIconBadgeId(user), + SOLID_COLORED, + () -> getDefaultUserIconBadge(user)); + Drawable badge = new LauncherIcons(mContext).getBadgeDrawable( - getUserManager().getUserIconBadgeResId(user.getIdentifier()), + badgeForeground, getUserBadgeColor(user, false)); return getBadgedDrawable(icon, badge, null, true); } + private String getUpdatableUserIconBadgeId(UserHandle user) { + return getUserManager().isManagedProfile(user.getIdentifier()) + ? WORK_PROFILE_ICON_BADGE : UNDEFINED; + } + + private Drawable getDefaultUserIconBadge(UserHandle user) { + return mContext.getDrawable(getUserManager().getUserIconBadgeResId(user.getIdentifier())); + } + @Override public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user, Rect badgeLocation, int badgeDensity) { @@ -1913,13 +1942,28 @@ public class ApplicationPackageManager extends PackageManager { if (badgeColor == null) { return null; } - Drawable badgeForeground = getDrawableForDensity( - getUserManager().getUserBadgeResId(user.getIdentifier()), density); + + final Drawable badgeForeground = getDevicePolicyManager().getDrawableForDensity( + getUpdatableUserBadgeId(user), + SOLID_COLORED, + density, + () -> getDefaultUserBadgeForDensity(user, density)); + badgeForeground.setTint(getUserBadgeColor(user, false)); Drawable badge = new LayerDrawable(new Drawable[] {badgeColor, badgeForeground }); return badge; } + private String getUpdatableUserBadgeId(UserHandle user) { + return getUserManager().isManagedProfile(user.getIdentifier()) + ? WORK_PROFILE_ICON : UNDEFINED; + } + + private Drawable getDefaultUserBadgeForDensity(UserHandle user, int density) { + return getDrawableForDensity( + getUserManager().getUserBadgeResId(user.getIdentifier()), density); + } + /** * Returns the badge color based on whether device has dark theme enabled or not. */ @@ -1928,14 +1972,24 @@ public class ApplicationPackageManager extends PackageManager { if (!hasUserBadge(user.getIdentifier())) { return null; } - Drawable badge = getDrawableForDensity( - getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density); + + final Drawable badge = getDevicePolicyManager().getDrawableForDensity( + getUpdatableUserBadgeId(user), + SOLID_NOT_COLORED, + density, + () -> getDefaultUserBadgeNoBackgroundForDensity(user, density)); + if (badge != null) { badge.setTint(getUserBadgeColor(user, true)); } return badge; } + private Drawable getDefaultUserBadgeNoBackgroundForDensity(UserHandle user, int density) { + return getDrawableForDensity( + getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density); + } + private Drawable getDrawableForDensity(int drawableId, int density) { if (density <= 0) { density = mContext.getResources().getDisplayMetrics().densityDpi; diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java index db58c215ffe2..7845b6a6588e 100644 --- a/core/java/android/app/AsyncNotedAppOp.java +++ b/core/java/android/app/AsyncNotedAppOp.java @@ -37,7 +37,8 @@ import com.android.internal.util.Preconditions; @Immutable @DataClass(genEqualsHashCode = true, genAidl = true, - genHiddenConstructor = true) + genHiddenConstructor = true, + genToString = true) // - We don't expose the opCode, but rather the public name of the op, hence use a non-standard // getter @DataClass.Suppress({"getOpCode"}) @@ -70,9 +71,13 @@ public final class AsyncNotedAppOp implements Parcelable { Preconditions.checkArgumentInRange(mOpCode, 0, AppOpsManager._NUM_OP - 1, "opCode"); } + private String opCodeToString() { + return getOp(); + } + - // Code below generated by codegen v1.0.20. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -160,6 +165,21 @@ public final class AsyncNotedAppOp implements Parcelable { @Override @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "AsyncNotedAppOp { " + + "opCode = " + opCodeToString() + ", " + + "notingUid = " + mNotingUid + ", " + + "attributionTag = " + mAttributionTag + ", " + + "message = " + mMessage + ", " + + "time = " + mTime + + " }"; + } + + @Override + @DataClass.Generated.Member public boolean equals(@Nullable Object o) { // You can override field equality logic by defining either of the methods like: // boolean fieldNameEquals(AsyncNotedAppOp other) { ... } @@ -261,10 +281,10 @@ public final class AsyncNotedAppOp implements Parcelable { }; @DataClass.Generated( - time = 1604456255752L, - codegenVersion = "1.0.20", + time = 1643320606160L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java", - inputSignatures = "private final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.IntRange int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.CurrentTimeMillisLong long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nprivate void onConstructed()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)") + inputSignatures = "private final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.IntRange int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.CurrentTimeMillisLong long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nprivate void onConstructed()\nprivate java.lang.String opCodeToString()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f3315a8dc089..8181a74e8f95 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1997,7 +1997,8 @@ class ContextImpl extends Context { private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, String instanceName, Handler handler, Executor executor, UserHandle user) { - // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser. + // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser and + // ActivityManagerService.LocalService.startAndBindSupplementalProcessService IServiceConnection sd; if (conn == null) { throw new IllegalArgumentException("connection is null"); @@ -2023,10 +2024,10 @@ class ContextImpl extends Context { flags |= BIND_WAIVE_PRIORITY; } service.prepareToLeaveProcess(this); - int res = ActivityManager.getService().bindIsolatedService( - mMainThread.getApplicationThread(), getActivityToken(), service, - service.resolveTypeIfNeeded(getContentResolver()), - sd, flags, instanceName, getOpPackageName(), user.getIdentifier()); + int res = ActivityManager.getService().bindServiceInstance( + mMainThread.getApplicationThread(), getActivityToken(), service, + service.resolveTypeIfNeeded(getContentResolver()), + sd, flags, instanceName, getOpPackageName(), user.getIdentifier()); if (res < 0) { throw new SecurityException( "Not allowed to bind to service " + service); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 82c419affac2..e4ef12c250ab 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -169,7 +169,7 @@ interface IActivityManager { int bindService(in IApplicationThread caller, in IBinder token, in Intent service, in String resolvedType, in IServiceConnection connection, int flags, in String callingPackage, int userId); - int bindIsolatedService(in IApplicationThread caller, in IBinder token, in Intent service, + int bindServiceInstance(in IApplicationThread caller, in IBinder token, in Intent service, in String resolvedType, in IServiceConnection connection, int flags, in String instanceName, in String callingPackage, int userId); void updateServiceGroup(in IServiceConnection connection, int group, int importance); diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 1714229486e4..77657d58cc4c 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -113,6 +113,7 @@ oneway interface IApplicationThread { in ParcelFileDescriptor fd, in RemoteCallback finishCallback); void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix, in String[] args); + void dumpResources(in ParcelFileDescriptor fd, in RemoteCallback finishCallback); void clearDnsCache(); void updateHttpProxy(); void setCoreSettings(in Bundle coreSettings); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 14afd0fcebf8..5d1f4df4359e 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -746,7 +746,7 @@ public class KeyguardManager { if (!hasPermission(Manifest.permission.SET_INITIAL_LOCK)) { throw new SecurityException("Requires SET_INITIAL_LOCK permission."); } - return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + return true; } private boolean hasPermission(String permission) { @@ -814,6 +814,8 @@ public class KeyguardManager { /** * Set the lockscreen password after validating against its expected complexity level. * + * Below {@link android.os.Build.VERSION_CODES#S_V2}, this API will only work + * when {@link PackageManager.FEATURE_AUTOMOTIVE} is present. * @param lockType - type of lock as specified in {@link LockTypes} * @param password - password to validate; this has the same encoding * as the output of String#getBytes diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 4e32e9a41869..56c725eb81f9 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -746,6 +746,10 @@ public final class LoadedApk { // default linker namespace. continue; } + if (info.isSdk()) { + // SDKs are not loaded automatically. + continue; + } if (libsToLoadAfter.contains(info.getName())) { if (DEBUG) { Slog.v(ActivityThread.TAG, diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index d57c288165d5..6a147720f8e9 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -17,6 +17,10 @@ package android.app; import static android.annotation.Dimension.DP; +import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION; +import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; +import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED; +import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; import static android.graphics.drawable.Icon.TYPE_URI; import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; @@ -39,6 +43,7 @@ import android.annotation.StyleableRes; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; +import android.app.admin.DevicePolicyManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -71,6 +76,7 @@ import android.os.Parcelable; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; import android.provider.Settings; import android.text.BidiFormatter; import android.text.SpannableStringBuilder; @@ -5046,6 +5052,18 @@ public class Notification implements Parcelable } // Note: This assumes that the current user can read the profile badge of the // originating user. + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getDrawable( + getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION, + this::getDefaultProfileBadgeDrawable); + } + + private String getUpdatableProfileBadgeId() { + return mContext.getSystemService(UserManager.class).isManagedProfile() + ? WORK_PROFILE_ICON : UNDEFINED; + } + + private Drawable getDefaultProfileBadgeDrawable() { return mContext.getPackageManager().getUserBadgeForDensityNoBackground( new UserHandle(mContext.getUserId()), 0); } diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index ec8d989c61d4..715de14345f7 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -16,7 +16,11 @@ package android.app; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -27,9 +31,10 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastPrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -104,17 +109,21 @@ import java.util.concurrent.atomic.AtomicLong; * <pre> * public class ActivityThread { * ... + * private final PropertyInvalidatedCache.QueryHandler<Integer, Birthday> mBirthdayQuery = + * new PropertyInvalidatedCache.QueryHandler<Integer, Birthday>() { + * {@literal @}Override + * public Birthday apply(Integer) { + * return GetService("birthdayd").getUserBirthday(userId); + * } + * }; * private static final int BDAY_CACHE_MAX = 8; // Maximum birthdays to cache * private static final String BDAY_CACHE_KEY = "cache_key.birthdayd"; * private final PropertyInvalidatedCache<Integer, Birthday%> mBirthdayCache = new - * PropertyInvalidatedCache<Integer, Birthday%>(BDAY_CACHE_MAX, BDAY_CACHE_KEY) { - * {@literal @}Override - * protected Birthday recompute(Integer userId) { - * return GetService("birthdayd").getUserBirthday(userId); - * } - * }; + * PropertyInvalidatedCache<Integer, Birthday%>( + * BDAY_CACHE_MAX, MODULE_SYSTEM, "getUserBirthday", mBirthdayQuery); + * * public void disableUserBirthdayCache() { - * mBirthdayCache.disableLocal(); + * mBirthdayCache.disableForCurrentProcess(); * } * public void invalidateUserBirthdayCache() { * mBirthdayCache.invalidateCache(); @@ -221,10 +230,124 @@ import java.util.concurrent.atomic.AtomicLong; * * @param <Query> The class used to index cache entries: must be hashable and comparable * @param <Result> The class holding cache entries; use a boxed primitive if possible - * - * {@hide} + * @hide */ -public abstract class PropertyInvalidatedCache<Query, Result> { +@SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) +@TestApi +public class PropertyInvalidatedCache<Query, Result> { + /** + * This is a configuration class that customizes a cache instance. + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public static abstract class QueryHandler<Q,R> { + /** + * Compute a result given a query. The semantics are those of Functor. + */ + public abstract @Nullable R apply(@NonNull Q query); + + /** + * Return true if a query should not use the cache. The default implementation + * always uses the cache. + */ + public boolean shouldBypassCache(@NonNull Q query) { + return false; + } + }; + + /** + * The system properties used by caches should be of the form <prefix>.<module>.<api>, + * where the prefix is "cache_key", the module is one of the constants below, and the + * api is any string. The ability to write the property (which happens during + * invalidation) depends on SELinux rules; these rules are defined against + * <prefix>.<module>. Therefore, the module chosen for a cache property must match + * the permissions granted to the processes that contain the corresponding caches. + * @hide + */ + @IntDef(prefix = { "MODULE_" }, value = { + MODULE_TEST, + MODULE_SYSTEM, + MODULE_BLUETOOTH + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Module {} + + /** + * The module used for unit tests and cts tests. It is expected that no process in + * the system has permissions to write properties with this module. + * @hide + */ + @TestApi + public static final int MODULE_TEST = 0; + + /** + * The module used for system server/framework caches. This is not visible outside + * the system processes. + * @hide + */ + @TestApi + public static final int MODULE_SYSTEM = 1; + + /** + * The module used for bluetooth caches. + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public static final int MODULE_BLUETOOTH = 2; + + // A static array mapping module constants to strings. + private final static String[] sModuleNames = + { "test", "system_server", "bluetooth" }; + + /** + * Construct a system property that matches the rules described above. The module is + * one of the permitted values above. The API is a string that is a legal Java simple + * identifier. The api is modified to conform to the system property style guide by + * replacing every upper case letter with an underscore and the lower case equivalent. + * There is no requirement that the apiName be the name of an actual API. + * + * Be aware that SystemProperties has a maximum length which is private to the + * implementation. The current maximum is 92 characters. If this method creates a + * property name that is too long, SystemProperties.set() will fail without a good + * error message. + * @hide + */ + @TestApi + public static @NonNull String createPropertyName(@Module int module, @NonNull String apiName) { + char[] api = apiName.toCharArray(); + int upper = 0; + for (int i = 0; i < api.length; i++) { + if (Character.isUpperCase(api[i])) { + upper++; + } + } + char[] suffix = new char[api.length + upper]; + int j = 0; + for (int i = 0; i < api.length; i++) { + if (Character.isJavaIdentifierPart(api[i])) { + if (Character.isUpperCase(api[i])) { + suffix[j++] = '_'; + suffix[j++] = Character.toLowerCase(api[i]); + } else { + suffix[j++] = api[i]; + } + } else { + throw new IllegalArgumentException("invalid api name"); + } + } + + String moduleName = null; + try { + moduleName = sModuleNames[module]; + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException("invalid module " + module); + } + + return "cache_key." + moduleName + "." + new String(suffix); + } + /** * Reserved nonce values. Use isReservedNonce() to test for a reserved value. Note * that all values cause the cache to be skipped. @@ -335,6 +458,25 @@ public abstract class PropertyInvalidatedCache<Query, Result> { */ private final String mCacheName; + /** + * The function that computes a Result, given a Query. This function is called on a + * cache miss. + */ + private QueryHandler<Query, Result> mComputer; + + /** + * A default function that delegates to the deprecated recompute() method. + */ + private static class DefaultComputer<Query, Result> extends QueryHandler<Query, Result> { + final PropertyInvalidatedCache<Query, Result> mCache; + DefaultComputer(PropertyInvalidatedCache<Query, Result> cache) { + mCache = cache; + } + public Result apply(Query query) { + return mCache.recompute(query); + } + } + @GuardedBy("mLock") private final LinkedHashMap<Query, Result> mCache; @@ -359,8 +501,13 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * property name. New clients should prefer the constructor that takes an explicit * cache name. * + * TODO(216112648): deprecate this as a public interface, in favor of the four-argument + * constructor. + * * @param maxEntries Maximum number of entries to cache; LRU discard * @param propertyName Name of the system property holding the cache invalidation nonce. + * + * @hide */ public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) { this(maxEntries, propertyName, propertyName); @@ -369,32 +516,73 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Make a new property invalidated cache. * + * TODO(216112648): deprecate this as a public interface, in favor of the four-argument + * constructor. + * * @param maxEntries Maximum number of entries to cache; LRU discard * @param propertyName Name of the system property holding the cache invalidation nonce * @param cacheName Name of this cache in debug and dumpsys + * @hide */ public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName, @NonNull String cacheName) { mPropertyName = propertyName; mCacheName = cacheName; mMaxEntries = maxEntries; - mCache = new LinkedHashMap<Query, Result>( + mComputer = new DefaultComputer<>(this); + mCache = createMap(); + registerCache(); + } + + /** + * Make a new property invalidated cache. The key is computed from the module and api + * parameters. + * + * @param maxEntries Maximum number of entries to cache; LRU discard + * @param module The module under which the cache key should be placed. + * @param api The api this cache front-ends. The api must be a Java identifier but + * need not be an actual api. + * @param cacheName Name of this cache in debug and dumpsys + * @param computer The code to compute values that are not in the cache. + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public PropertyInvalidatedCache(int maxEntries, @Module int module, @NonNull String api, + @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) { + mPropertyName = createPropertyName(module, api); + mCacheName = cacheName; + mMaxEntries = maxEntries; + mComputer = computer; + mCache = createMap(); + registerCache(); + } + + // Create a map. This should be called only from the constructor. + private LinkedHashMap<Query, Result> createMap() { + return new LinkedHashMap<Query, Result>( 2 /* start small */, 0.75f /* default load factor */, true /* LRU access order */) { + @GuardedBy("mLock") @Override protected boolean removeEldestEntry(Map.Entry eldest) { final int size = size(); if (size > mHighWaterMark) { mHighWaterMark = size; } - if (size > maxEntries) { + if (size > mMaxEntries) { mMissOverflow++; return true; } return false; } - }; + }; + } + + // Register the map in the global list. If the cache is disabled globally, disable it + // now. + private void registerCache() { synchronized (sCorkLock) { sCaches.put(this, null); if (sDisabledKeys.contains(mCacheName)) { @@ -418,8 +606,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Enable or disable testing. The testing property map is cleared every time this * method is called. + * @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @TestApi public static void setTestMode(boolean mode) { sTesting = mode; synchronized (sTestingPropertyMap) { @@ -431,13 +620,22 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Enable testing the specific cache key. Only keys in the map are subject to testing. * There is no method to stop testing a property name. Just disable the test mode. */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public static void testPropertyName(@NonNull String name) { + private static void testPropertyName(@NonNull String name) { synchronized (sTestingPropertyMap) { sTestingPropertyMap.put(name, (long) NONCE_UNSET); } } + /** + * Enable testing the specific cache key. Only keys in the map are subject to testing. + * There is no method to stop testing a property name. Just disable the test mode. + * @hide + */ + @TestApi + public void testPropertyName() { + testPropertyName(mPropertyName); + } + // Read the system property associated with the current cache. This method uses the // handle for faster reading. private long getCurrentNonce() { @@ -490,6 +688,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Forget all cached values. + * TODO(216112648) remove this as a public API. Clients should invalidate caches, not clear + * them. + * @hide */ public final void clear() { synchronized (mLock) { @@ -505,22 +706,29 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Fetch a result from scratch in case it's not in the cache at all. Called unlocked: may * block. If this function returns null, the result of the cache query is null. There is no * "negative cache" in the query: we don't cache null results at all. + * TODO(216112648): deprecate this as a public interface, in favor of an instance of + * QueryHandler. + * @hide */ - public abstract @NonNull Result recompute(@NonNull Query query); + public Result recompute(@NonNull Query query) { + return mComputer.apply(query); + } /** * Return true if the query should bypass the cache. The default behavior is to * always use the cache but the method can be overridden for a specific class. + * TODO(216112648): deprecate this as a public interface, in favor of an instance of + * QueryHandler. + * @hide */ public boolean bypass(@NonNull Query query) { - return false; + return mComputer.shouldBypassCache(query); } /** - * Determines if a pair of responses are considered equal. Used to determine whether a - * cache is inadvertently returning stale results when VERIFY is set to true. Some - * existing clients override this method, but it is now deprecated in favor of a valid - * equals() method on the Result class. + * Determines if a pair of responses are considered equal. Used to determine whether + * a cache is inadvertently returning stale results when VERIFY is set to true. + * @hide */ public boolean resultEquals(Result cachedResult, Result fetchedResult) { // If a service crashes and returns a null result, the cached value remains valid. @@ -541,6 +749,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * the meantime (if the nonce has changed in the meantime, we drop the cache and try the * whole query again), or 3) null, which causes the old value to be removed from the cache * and null to be returned as the result of the cache query. + * @hide */ protected Result refresh(Result oldResult, Query query) { return oldResult; @@ -551,7 +760,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * testing. To disable a cache in normal code, use disableLocal(). * @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @TestApi public final void disableInstance() { synchronized (mLock) { mDisabled = true; @@ -580,9 +789,10 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * disabled remain disabled (the "disabled" setting is sticky). However, new caches * with this name will not be disabled. It is not an error if the cache name is not * found in the list of disabled caches. + * @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public final void clearDisableLocal() { + @TestApi + public final void forgetDisableLocal() { synchronized (sCorkLock) { sDisabledKeys.remove(mCacheName); } @@ -592,25 +802,43 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Disable this cache in the current process, and all other caches that use the same * name. This does not affect caches that have a different name but use the same * property. + * TODO(216112648) Remove this in favor of disableForCurrentProcess(). + * @hide */ public final void disableLocal() { + disableForCurrentProcess(); + } + + /** + * Disable this cache in the current process, and all other caches that use the same + * name. This does not affect caches that have a different name but use the same + * property. + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public final void disableForCurrentProcess() { disableLocal(mCacheName); } /** - * Return whether the cache is disabled in this process. + * Return whether a cache instance is disabled. + * @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public final boolean isDisabledLocal() { + @TestApi + public final boolean isDisabled() { return mDisabled || !sEnabled; } /** * Get a value from the cache or recompute it. + * @hide */ - public @NonNull Result query(@NonNull Query query) { + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public final @Nullable Result query(@NonNull Query query) { // Let access to mDisabled race: it's atomic anyway. - long currentNonce = (!isDisabledLocal()) ? getCurrentNonce() : NONCE_DISABLED; + long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED; if (bypass(query)) { currentNonce = NONCE_BYPASS; } @@ -724,8 +952,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * When multiple caches share a single property value, using an instance method on one of * the cache objects to invalidate all of the cache objects becomes confusing and you should * just use the static version of this function. + * @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @TestApi public final void disableSystemWide() { disableSystemWide(mPropertyName); } @@ -746,16 +975,33 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Non-static convenience version of invalidateCache() for situations in which only a single * PropertyInvalidatedCache is keyed on a particular property value. + * @hide */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi public final void invalidateCache() { invalidateCache(mPropertyName); } /** + * Invalidate caches in all processes that are keyed for the module and api. + * @hide + */ + @SystemApi(client=SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public static void invalidateCache(@Module int module, @NonNull String api) { + invalidateCache(createPropertyName(module, api)); + } + + /** * Invalidate PropertyInvalidatedCache caches in all processes that are keyed on * {@var name}. This function is synchronous: caches are invalidated upon return. * + * TODO(216112648) make this method private in favor of the two-argument (module, api) + * override. + * * @param name Name of the cache-key property to invalidate + * @hide */ public static void invalidateCache(@NonNull String name) { if (!sEnabled) { @@ -824,6 +1070,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * corkInvalidations() and uncorkInvalidations() must be called in pairs. * * @param name Name of the cache-key property to cork + * @hide */ public static void corkInvalidations(@NonNull String name) { if (!sEnabled) { @@ -871,6 +1118,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * transitioning it to normal operation (unless explicitly disabled system-wide). * * @param name Name of the cache-key property to uncork + * @hide */ public static void uncorkInvalidations(@NonNull String name) { if (!sEnabled) { @@ -916,6 +1164,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * The auto-cork delay is configurable but it should not be too long. The purpose of * the delay is to minimize the number of times a server writes to the system property * when invalidating the cache. One write every 50ms does not hurt system performance. + * @hide */ public static final class AutoCorker { public static final int DEFAULT_AUTO_CORK_DELAY_MS = 50; @@ -1043,6 +1292,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * Return the query as a string, to be used in debug messages. New clients should not * override this, but should instead add the necessary toString() method to the Query * class. + * TODO(216112648) add a method in the QueryHandler and deprecate this API. + * @hide */ protected @NonNull String queryToString(@NonNull Query query) { return Objects.toString(query); @@ -1054,8 +1305,9 @@ public abstract class PropertyInvalidatedCache<Query, Result> { * process does not have privileges to write SystemProperties. Once disabled it is not * possible to re-enable caching in the current process. If a client wants to * temporarily disable caching, use the corking mechanism. + * @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @TestApi public static void disableForTestMode() { Log.d(TAG, "disabling all caches in the process"); sEnabled = false; @@ -1064,10 +1316,11 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Report the disabled status of this cache instance. The return value does not * reflect status of the property key. + * @hide */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @TestApi public boolean getDisabledState() { - return isDisabledLocal(); + return isDisabled(); } /** @@ -1133,7 +1386,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } /** - * Dumps the contents of every cache in the process to the provided ParcelFileDescriptor. + * Dumps contents of every cache in the process to the provided ParcelFileDescriptor. + * @hide */ public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) { ArrayList<PropertyInvalidatedCache> activeCaches; @@ -1174,6 +1428,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { /** * Trim memory by clearing all the caches. + * @hide */ public static void onTrimMemory() { for (PropertyInvalidatedCache pic : getActiveCaches()) { diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java index 7c0c08a7fc35..f156b30d5050 100644 --- a/core/java/android/app/SyncNotedAppOp.java +++ b/core/java/android/app/SyncNotedAppOp.java @@ -40,7 +40,8 @@ import com.android.internal.util.DataClass; @DataClass( genEqualsHashCode = true, genAidl = true, - genConstructor = false + genConstructor = false, + genToString = true ) @DataClass.Suppress({"getOpCode", "getOpMode"}) public final class SyncNotedAppOp implements Parcelable { @@ -118,6 +119,10 @@ public final class SyncNotedAppOp implements Parcelable { return mOpMode; } + private String opCodeToString() { + return getOp(); + } + // Code below generated by codegen v1.0.23. @@ -153,6 +158,20 @@ public final class SyncNotedAppOp implements Parcelable { @Override @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "SyncNotedAppOp { " + + "opMode = " + mOpMode + ", " + + "opCode = " + opCodeToString() + ", " + + "attributionTag = " + mAttributionTag + ", " + + "packageName = " + mPackageName + + " }"; + } + + @Override + @DataClass.Generated.Member public boolean equals(@Nullable Object o) { // You can override field equality logic by defining either of the methods like: // boolean fieldNameEquals(SyncNotedAppOp other) { ... } @@ -245,10 +264,10 @@ public final class SyncNotedAppOp implements Parcelable { }; @DataClass.Generated( - time = 1619711733947L, + time = 1643320427700L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java", - inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false)") + inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nprivate java.lang.String opCodeToString()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 18f9379c4111..3d2c03dc4e24 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -214,6 +214,12 @@ public class TaskInfo { public boolean topActivityInSizeCompat; /** + * Whether the direct top activity is eligible for letterbox education. + * @hide + */ + public boolean topActivityEligibleForLetterboxEducation; + + /** * Whether this task is resizable. Unlike {@link #resizeMode} (which is what the top activity * supports), this is what the system actually uses for resizability based on other policy and * developer options. @@ -398,7 +404,8 @@ public class TaskInfo { /** @hide */ public boolean hasCompatUI() { - return hasCameraCompatControl() || topActivityInSizeCompat; + return hasCameraCompatControl() || topActivityInSizeCompat + || topActivityEligibleForLetterboxEducation; } /** @@ -460,6 +467,8 @@ public class TaskInfo { return displayId == that.displayId && taskId == that.taskId && topActivityInSizeCompat == that.topActivityInSizeCompat + && topActivityEligibleForLetterboxEducation + == that.topActivityEligibleForLetterboxEducation && cameraCompatControlState == that.cameraCompatControlState // Bounds are important if top activity has compat controls. && (!hasCompatUI() || configuration.windowConfiguration.getBounds() @@ -507,6 +516,7 @@ public class TaskInfo { isVisible = source.readBoolean(); isSleeping = source.readBoolean(); topActivityInSizeCompat = source.readBoolean(); + topActivityEligibleForLetterboxEducation = source.readBoolean(); mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR); displayAreaFeatureId = source.readInt(); cameraCompatControlState = source.readInt(); @@ -551,6 +561,7 @@ public class TaskInfo { dest.writeBoolean(isVisible); dest.writeBoolean(isSleeping); dest.writeBoolean(topActivityInSizeCompat); + dest.writeBoolean(topActivityEligibleForLetterboxEducation); dest.writeTypedObject(mTopActivityLocusId, flags); dest.writeInt(displayAreaFeatureId); dest.writeInt(cameraCompatControlState); @@ -585,6 +596,8 @@ public class TaskInfo { + " isVisible=" + isVisible + " isSleeping=" + isSleeping + " topActivityInSizeCompat=" + topActivityInSizeCompat + + " topActivityEligibleForLetterboxEducation= " + + topActivityEligibleForLetterboxEducation + " locusId=" + mTopActivityLocusId + " displayAreaFeatureId=" + displayAreaFeatureId + " cameraCompatControlState=" diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3960f4e1518e..a4227a4d3074 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -454,6 +454,52 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> * </ul> * + * <p>Once the device admin app is set as the device owner, the following APIs are available for + * managing polices on the device: + * <ul> + * <li>{@link #isDeviceManaged()}</li> + * <li>{@link #isUninstallBlocked(ComponentName, String)}</li> + * <li>{@link #setUninstallBlocked(ComponentName, String, boolean)}</li> + * <li>{@link #setUserControlDisabledPackages(ComponentName, List)}</li> + * <li>{@link #getUserControlDisabledPackages(ComponentName)}</li> + * <li>{@link #setOrganizationName(ComponentName, CharSequence)}</li> + * <li>{@link #setShortSupportMessage(ComponentName, CharSequence)}</li> + * <li>{@link #isBackupServiceEnabled(ComponentName)}</li> + * <li>{@link #setBackupServiceEnabled(ComponentName, boolean)}</li> + * <li>{@link #isLockTaskPermitted(String)}</li> + * <li>{@link #setLockTaskFeatures(ComponentName, int)}, where the following lock task features + * can be set (otherwise a {@link SecurityException} will be thrown):</li> + * <ul> + * <li>{@link #LOCK_TASK_FEATURE_SYSTEM_INFO}</li> + * <li>{@link #LOCK_TASK_FEATURE_KEYGUARD}</li> + * <li>{@link #LOCK_TASK_FEATURE_HOME}</li> + * <li>{@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS}</li> + * <li>{@link #LOCK_TASK_FEATURE_NOTIFICATIONS}</li> + * </ul> + * <li>{@link #setLockTaskPackages(ComponentName, String[])}</li> + * <li>{@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}</li> + * <li>{@link #clearPackagePersistentPreferredActivities(ComponentName, String)} </li> + * <li>{@link #wipeData(int)}</li> + * <li>{@link #isDeviceOwnerApp(String)}</li> + * <li>{@link #clearDeviceOwnerApp(String)}</li> + * <li>{@link #setPermissionGrantState(ComponentName, String, String, int)}, where + * {@link permission#READ_PHONE_STATE} is the <b>only</b> permission that can be + * {@link #PERMISSION_GRANT_STATE_GRANTED}, {@link #PERMISSION_GRANT_STATE_DENIED}, or + * {@link #PERMISSION_GRANT_STATE_DEFAULT} and can <b>only</b> be applied to the device admin + * app (otherwise a {@link SecurityException} will be thrown)</li> + * <li>{@link #addUserRestriction(ComponentName, String)}, where the following user restrictions + * are permitted (otherwise a {@link SecurityException} will be thrown):</li> + * <ul> + * <li>{@link UserManager#DISALLOW_ADD_USER}</li> + * <li>{@link UserManager#DISALLOW_DEBUGGING_FEATURES}</li> + * <li>{@link UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES}</li> + * <li>{@link UserManager#DISALLOW_SAFE_BOOT}</li> + * <li>{@link UserManager#DISALLOW_CONFIG_DATE_TIME}</li> + * <li>{@link UserManager#DISALLOW_OUTGOING_CALLS}</li> + * </ul> + * <li>{@link #clearUserRestriction(ComponentName, String)}</li> + * </ul> + * * @hide */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) @@ -587,7 +633,7 @@ public class DevicePolicyManager { * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} to signal that an update * to the role holder is required. * - * <p>This result code must be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}. + * <p>This result code can be accompanied by {@link #EXTRA_ROLE_HOLDER_STATE}. * * @hide */ @@ -595,15 +641,19 @@ public class DevicePolicyManager { public static final int RESULT_UPDATE_ROLE_HOLDER = 2; /** - * A {@link Bundle} extra which describes the state of the role holder at the time when it - * returns {@link #RESULT_UPDATE_ROLE_HOLDER}. + * A {@link PersistableBundle} extra which the role holder can use to describe its own state + * when it returns {@link #RESULT_UPDATE_ROLE_HOLDER}. * - * <p>After the update completes, the role holder's {@link - * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link + * <p>If {@link #RESULT_UPDATE_ROLE_HOLDER} was accompanied by this extra, after the update + * completes, the role holder's {@link #ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE} or {@link * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent will be relaunched, * which will contain this extra. It is the role holder's responsibility to restore its * state from this extra. * + * <p>The content of this {@link PersistableBundle} is entirely up to the role holder. It + * should contain anything the role holder needs to restore its original state when it gets + * restarted. + * * @hide */ @SystemApi @@ -14577,6 +14627,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public void setDeviceOwnerType(@NonNull ComponentName admin, @DeviceOwnerType int deviceOwnerType) { throwIfParentInstance("setDeviceOwnerType"); @@ -14600,6 +14651,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi @DeviceOwnerType public int getDeviceOwnerType(@NonNull ComponentName admin) { throwIfParentInstance("getDeviceOwnerType"); @@ -14767,6 +14819,11 @@ public class DevicePolicyManager { * The device may not connect to networks that do not meet the minimum security level. * If the current network does not meet the minimum security level set, it will be disconnected. * + * The following shows the Wi-Fi security levels from the lowest to the highest security level: + * {@link #WIFI_SECURITY_OPEN} + * {@link #WIFI_SECURITY_PERSONAL} + * {@link #WIFI_SECURITY_ENTERPRISE_EAP} + * {@link #WIFI_SECURITY_ENTERPRISE_192} * * @param level minimum security level * @throws SecurityException if the caller is not a device owner or a profile owner on @@ -14943,8 +15000,8 @@ public class DevicePolicyManager { * <p>Also returns the drawable from {@code defaultDrawableLoader} if * {@link DevicePolicyResources.Drawables#UNDEFINED} was passed. * - * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a - * {@link NullPointerException} is thrown. + * <p>Calls to this API will not return {@code null} unless no updated drawable was found + * and the call to {@code defaultDrawableLoader} returned {@code null}. * * <p>This API uses the screen density returned from {@link Resources#getConfiguration()}, to * set a different value use @@ -14961,7 +15018,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @NonNull + @Nullable public Drawable getDrawable( @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId, @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle, @@ -14977,8 +15034,8 @@ public class DevicePolicyManager { * {@link #getDrawable(String, String, Callable)} * if an override was set for that specific source. * - * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a - * {@link NullPointerException} is thrown. + * <p>Calls to this API will not return {@code null} unless no updated drawable was found + * and the call to {@code defaultDrawableLoader} returned {@code null}. * * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get * notified when a resource has been updated. @@ -14989,7 +15046,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @NonNull + @Nullable public Drawable getDrawable( @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId, @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle, @@ -15032,8 +15089,8 @@ public class DevicePolicyManager { * Similar to {@link #getDrawable(String, String, Callable)}, but also accepts * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. * - * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a - * {@link NullPointerException} is thrown. + * <p>Calls to this API will not return {@code null} unless no updated drawable was found + * and the call to {@code defaultDrawableLoader} returned {@code null}. * * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get * notified when a resource has been updated. @@ -15046,7 +15103,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @NonNull + @Nullable public Drawable getDrawableForDensity( @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId, @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle, @@ -15064,8 +15121,8 @@ public class DevicePolicyManager { * Similar to {@link #getDrawable(String, String, String, Callable)}, but also accepts * {@code density}. See {@link Resources#getDrawableForDensity(int, int, Resources.Theme)}. * - * <p>{@code defaultDrawableLoader} must return a non {@code null} {@link Drawable}, otherwise a - * {@link NullPointerException} is thrown. + * <p>Calls to this API will not return {@code null} unless no updated drawable was found + * and the call to {@code defaultDrawableLoader} returned {@code null}. * * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get * notified when a resource has been updated. @@ -15079,7 +15136,7 @@ public class DevicePolicyManager { * @param defaultDrawableLoader To get the default drawable if no updated drawable was set for * the provided params. */ - @NonNull + @Nullable public Drawable getDrawableForDensity( @NonNull @DevicePolicyResources.UpdatableDrawableId String drawableId, @NonNull @DevicePolicyResources.UpdatableDrawableStyle String drawableStyle, @@ -15118,7 +15175,7 @@ public class DevicePolicyManager { /** * For each {@link DevicePolicyStringResource} item in {@code strings}, it updates the string * resource for {@link DevicePolicyStringResource#getStringId()} to the string with ID - * {@code callingPackageResourceId} (see {@link DevicePolicyResources.String}), meaning any + * {@code callingPackageResourceId} (see {@link DevicePolicyResources.Strings}), meaning any * system UI surface calling {@link #getString} with {@code stringId} will get * the new resource after this API is called. * @@ -15154,7 +15211,7 @@ public class DevicePolicyManager { /** * Removes the updated strings for the list of {@code stringIds} (see - * {@link DevicePolicyResources.String}) that was previously set by calling {@link #setStrings}, + * {@link DevicePolicyResources.Strings}) that was previously set by calling {@link #setStrings}, * meaning any subsequent calls to {@link #getString} for the provided IDs will * return the default string from {@code defaultStringLoader}. * @@ -15179,14 +15236,14 @@ public class DevicePolicyManager { /** * Returns the appropriate updated string for the {@code stringId} (see - * {@link DevicePolicyResources.String}) if one was set using + * {@link DevicePolicyResources.Strings}) if one was set using * {@link #setStrings}, otherwise returns the string from {@code defaultStringLoader}. * * <p>Also returns the string from {@code defaultStringLoader} if - * {@link DevicePolicyResources.String#INVALID_ID} was passed. + * {@link DevicePolicyResources.Strings#UNDEFINED} was passed. * - * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a - * {@link NullPointerException} is thrown. + * <p>Calls to this API will not return {@code null} unless no updated drawable was found + * and the call to {@code defaultStringLoader} returned {@code null}. * * <p>Callers should register for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to get * notified when a resource has been updated. @@ -15201,7 +15258,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @NonNull + @Nullable public String getString( @NonNull @DevicePolicyResources.UpdatableStringId String stringId, @NonNull Callable<String> defaultStringLoader) { @@ -15236,8 +15293,8 @@ public class DevicePolicyManager { * {@link java.util.Formatter} and {@link java.lang.String#format}, (see * {@link Resources#getString(int, Object...)}). * - * <p>{@code defaultStringLoader} must return a non {@code null} {@link String}, otherwise a - * {@link NullPointerException} is thrown. + * <p>Calls to this API will not return {@code null} unless no updated drawable was found + * and the call to {@code defaultStringLoader} returned {@code null}. * * @param stringId The IDs to get the updated resource for. * @param defaultStringLoader To get the default string if no updated string was set for @@ -15247,7 +15304,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @NonNull + @Nullable @SuppressLint("SamShouldBeLast") public String getString( @NonNull @DevicePolicyResources.UpdatableStringId String stringId, diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index 2ad201093d58..ac39cb4f1e23 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -93,6 +93,173 @@ import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.BLOC import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_PERSONAL_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_WORK_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.WORK_PROFILE_PAUSED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.PermissionController.BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.PermissionController.BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.PermissionController.FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.PermissionController.HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.PermissionController.LOCATION_AUTO_GRANTED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.PermissionController.WORK_PROFILE_DEFAULT_APPS_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_CATEGORY_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCESSIBILITY_WORK_ACCOUNT_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ACCOUNTS_SEARCH_KEYWORDS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_DEVICE_ADMIN_APP; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_DEVICE_ADMIN_APP_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVATE_THIS_DEVICE_ADMIN_APP; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ACTIVE_DEVICE_ADMIN_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTIONS_APPS_COUNT; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTIONS_APPS_COUNT_MINIMUM; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_CAMERA; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_LOCATION; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_ACCESS_MICROPHONE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_APPS_COUNT_ESTIMATED; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_APPS_INSTALLED; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_NONE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_CURRENT_INPUT_METHOD; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_DEFAULT_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_HTTP_PROXY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_ACTION_SET_INPUT_METHOD_NAME; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_LOCK_DEVICE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_APPS_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_BUG_REPORT_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_NETWORK_LOGS_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_SECURITY_LOGS_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_USAGE_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_SEE_WORK_DATA_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CAN_WIPE_DEVICE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_DEVICE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_PERSONAL_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ALWAYS_ON_VPN_WORK_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.APP_CAN_ACCESS_PERSONAL_DATA; +import static android.app.admin.DevicePolicyResources.Strings.Settings.APP_CAN_ACCESS_PERSONAL_PERMISSIONS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_DEVICE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_PERSONAL_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CA_CERTS_WORK_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_APPS_SEARCH_KEYWORDS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECTED_WORK_AND_PERSONAL_APPS_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECT_APPS_DIALOG_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONNECT_APPS_DIALOG_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTACT_YOUR_IT_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CROSS_PROFILE_CALENDAR_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CROSS_PROFILE_CALENDAR_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_ADMIN_POLICIES_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_ADMIN_SETTINGS_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_MANAGED_WITHOUT_NAME; +import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_MANAGED_WITH_NAME; +import static android.app.admin.DevicePolicyResources.Strings.Settings.DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.DISABLED_BY_IT_ADMIN_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ENTERPRISE_PRIVACY_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ERROR_MOVE_DEVICE_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_SETTINGS_FOR_WORK_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.FACE_UNLOCK_DISABLED; +import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_FOR_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED; +import static android.app.admin.DevicePolicyResources.Strings.Settings.FINGERPRINT_UNLOCK_DISABLED_EXPLANATION; +import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TEXT; +import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.HOW_TO_DISCONNECT_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT; +import static android.app.admin.DevicePolicyResources.Strings.Settings.INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT; +import static android.app.admin.DevicePolicyResources.Strings.Settings.IT_ADMIN_POLICY_DISABLING_INFO_URL; +import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_BY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO; +import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME; +import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGED_PROFILE_SETTINGS_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.MANAGE_DEVICE_ADMIN_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.NEW_DEVICE_ADMIN_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED; +import static android.app.admin.DevicePolicyResources.Strings.Settings.NO_DEVICE_ADMINS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.NUMBER_OF_DEVICE_ADMINS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.NUMBER_OF_DEVICE_ADMINS_NONE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ONLY_CONNECT_TRUSTED_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.OTHER_OPTIONS_DISABLED_BY_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Settings.PASSWORD_RECENTLY_USED; +import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_PROFILE_APP_SUBTEXT; +import static android.app.admin.DevicePolicyResources.Strings.Settings.PIN_RECENTLY_USED; +import static android.app.admin.DevicePolicyResources.Strings.Settings.REENTER_WORK_PROFILE_PASSWORD_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.REENTER_WORK_PROFILE_PIN_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION; +import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_AND_UNINSTALL_DEVICE_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_DEVICE_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_WORK_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SELECT_DEVICE_ADMIN_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_DIALOG_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_POSTSETUP_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_PROFILE_OWNER_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PASSWORD_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PATTERN_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PIN_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_DIALOG_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT; +import static android.app.admin.DevicePolicyResources.Strings.Settings.SHARING_REMOTE_BUGREPORT_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.UNINSTALL_DEVICE_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Settings.USER_ADMIN_POLICIES_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_ADMIN_POLICIES_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_ALARM_RINGTONE_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_APP_SUBTEXT; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PATTERN; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_PIN; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_REMOVE_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONFIRM_REMOVE_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONTACT_SEARCH_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_CONTACT_SEARCH_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_KEYBOARDS_AND_TOOLS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCATION_SWITCH_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCKED_NOTIFICATION_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_MANAGED_BY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOT_AVAILABLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_OFF_CONDITION_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PASSWORD_REQUIRED; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PRIVACY_POLICY_INFO; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_RINGTONE_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SECURITY_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_OFF_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SETTING_ON_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_DETAIL; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_UNIFY_LOCKS_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Settings.YOUR_ACCESS_TO_THIS_DEVICE_TITLE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT; @@ -159,7 +326,8 @@ public final class DevicePolicyResources { Drawables.WORK_PROFILE_OFF_ICON, Drawables.WORK_PROFILE_USER_ICON }) - public @interface UpdatableDrawableId {} + public @interface UpdatableDrawableId { + } /** * Identifiers to specify the desired style for the updatable device management system @@ -174,7 +342,8 @@ public final class DevicePolicyResources { Drawables.Style.OUTLINE, Drawables.Style.DEFAULT }) - public @interface UpdatableDrawableStyle {} + public @interface UpdatableDrawableStyle { + } /** * Identifiers to specify the location if the updatable device management system resource. @@ -191,7 +360,8 @@ public final class DevicePolicyResources { Drawables.Source.QUICK_SETTINGS, Drawables.Source.STATUS_BAR }) - public @interface UpdatableDrawableSource {} + public @interface UpdatableDrawableSource { + } /** * Resource identifiers used to update device management-related string resources. @@ -231,7 +401,7 @@ public final class DevicePolicyResources { PERSONAL_APP_SUSPENSION_TITLE, PERSONAL_APP_SUSPENSION_MESSAGE, PERSONAL_APP_SUSPENSION_SOON_MESSAGE, PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE, PRINTING_DISABLED_NAMED_ADMIN, LOCATION_CHANGED_TITLE, LOCATION_CHANGED_MESSAGE, - NETWORK_LOGGING_TITLE, NETWORK_LOGGING_MESSAGE, + NETWORK_LOGGING_TITLE, NETWORK_LOGGING_MESSAGE, NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, NOTIFICATION_CHANNEL_DEVICE_ADMIN, SWITCH_TO_WORK_LABEL, SWITCH_TO_PERSONAL_LABEL, FORWARD_INTENT_TO_WORK, FORWARD_INTENT_TO_PERSONAL, RESOLVER_WORK_PROFILE_NOT_SUPPORTED, RESOLVER_PERSONAL_TAB, @@ -257,7 +427,92 @@ public final class DevicePolicyResources { SWITCH_TO_WORK_MESSAGE, SWITCH_TO_PERSONAL_MESSAGE, BLOCKED_BY_ADMIN_TITLE, BLOCKED_FROM_PERSONAL_MESSAGE, BLOCKED_FROM_PERSONAL_MESSAGE, BLOCKED_FROM_WORK_MESSAGE, Strings.MediaProvider.WORK_PROFILE_PAUSED_TITLE, - WORK_PROFILE_PAUSED_MESSAGE + WORK_PROFILE_PAUSED_MESSAGE, + + // Settings Strings + FACE_SETTINGS_FOR_WORK_TITLE, WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE, + WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK, WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE, + WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE, + WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE, + WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE, + WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE, WORK_PROFILE_LOCK_ATTEMPTS_FAILED, + ACCESSIBILITY_CATEGORY_WORK, ACCESSIBILITY_CATEGORY_PERSONAL, + ACCESSIBILITY_WORK_ACCOUNT_TITLE, ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE, + WORK_PROFILE_LOCATION_SWITCH_TITLE, SET_WORK_PROFILE_PASSWORD_HEADER, + SET_WORK_PROFILE_PIN_HEADER, SET_WORK_PROFILE_PATTERN_HEADER, + CONFIRM_WORK_PROFILE_PASSWORD_HEADER, CONFIRM_WORK_PROFILE_PIN_HEADER, + CONFIRM_WORK_PROFILE_PATTERN_HEADER, REENTER_WORK_PROFILE_PASSWORD_HEADER, + REENTER_WORK_PROFILE_PIN_HEADER, WORK_PROFILE_CONFIRM_PATTERN, WORK_PROFILE_CONFIRM_PIN, + WORK_PROFILE_PASSWORD_REQUIRED, WORK_PROFILE_SECURITY_TITLE, + WORK_PROFILE_UNIFY_LOCKS_TITLE, WORK_PROFILE_UNIFY_LOCKS_SUMMARY, + WORK_PROFILE_UNIFY_LOCKS_DETAIL, WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT, + WORK_PROFILE_KEYBOARDS_AND_TOOLS, WORK_PROFILE_NOT_AVAILABLE, WORK_PROFILE_SETTING, + WORK_PROFILE_SETTING_ON_SUMMARY, WORK_PROFILE_SETTING_OFF_SUMMARY, REMOVE_WORK_PROFILE, + DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING, + WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING, WORK_PROFILE_CONFIRM_REMOVE_TITLE, + WORK_PROFILE_CONFIRM_REMOVE_MESSAGE, WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS, + WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER, WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE, + WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY, WORK_PROFILE_RINGTONE_TITLE, + WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE, WORK_PROFILE_ALARM_RINGTONE_TITLE, + WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY, + ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE, + ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE, + WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER, WORK_PROFILE_LOCKED_NOTIFICATION_TITLE, + WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE, + WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY, + WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED, CONNECTED_WORK_AND_PERSONAL_APPS_TITLE, + CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA, ONLY_CONNECT_TRUSTED_APPS, + HOW_TO_DISCONNECT_APPS, CONNECT_APPS_DIALOG_TITLE, CONNECT_APPS_DIALOG_SUMMARY, + APP_CAN_ACCESS_PERSONAL_DATA, APP_CAN_ACCESS_PERSONAL_PERMISSIONS, + INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT, + INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT, WORK_PROFILE_MANAGED_BY, MANAGED_BY, + WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING, DISABLED_BY_IT_ADMIN_TITLE, + CONTACT_YOUR_IT_ADMIN, WORK_PROFILE_ADMIN_POLICIES_WARNING, USER_ADMIN_POLICIES_WARNING, + DEVICE_ADMIN_POLICIES_WARNING, WORK_PROFILE_OFF_CONDITION_TITLE, + MANAGED_PROFILE_SETTINGS_TITLE, WORK_PROFILE_CONTACT_SEARCH_TITLE, + WORK_PROFILE_CONTACT_SEARCH_SUMMARY, CROSS_PROFILE_CALENDAR_TITLE, + CROSS_PROFILE_CALENDAR_SUMMARY, ALWAYS_ON_VPN_PERSONAL_PROFILE, ALWAYS_ON_VPN_DEVICE, + ALWAYS_ON_VPN_WORK_PROFILE, CA_CERTS_PERSONAL_PROFILE, CA_CERTS_WORK_PROFILE, + CA_CERTS_DEVICE, ADMIN_CAN_LOCK_DEVICE, ADMIN_CAN_WIPE_DEVICE, + ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE, + ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE, DEVICE_MANAGED_WITHOUT_NAME, + DEVICE_MANAGED_WITH_NAME, WORK_PROFILE_APP_SUBTEXT, PERSONAL_PROFILE_APP_SUBTEXT, + FINGERPRINT_FOR_WORK, FACE_UNLOCK_DISABLED, FINGERPRINT_UNLOCK_DISABLED, + FINGERPRINT_UNLOCK_DISABLED_EXPLANATION, PIN_RECENTLY_USED, PASSWORD_RECENTLY_USED, + MANAGE_DEVICE_ADMIN_APPS, NUMBER_OF_DEVICE_ADMINS_NONE, NUMBER_OF_DEVICE_ADMINS, + FORGOT_PASSWORD_TITLE, FORGOT_PASSWORD_TEXT, ERROR_MOVE_DEVICE_ADMIN, + DEVICE_ADMIN_SETTINGS_TITLE, REMOVE_DEVICE_ADMIN, UNINSTALL_DEVICE_ADMIN, + REMOVE_AND_UNINSTALL_DEVICE_ADMIN, SELECT_DEVICE_ADMIN_APPS, NO_DEVICE_ADMINS, + ACTIVATE_DEVICE_ADMIN_APP, ACTIVATE_THIS_DEVICE_ADMIN_APP, + ACTIVATE_DEVICE_ADMIN_APP_TITLE, NEW_DEVICE_ADMIN_WARNING, + NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED, ACTIVE_DEVICE_ADMIN_WARNING, + SET_PROFILE_OWNER_TITLE, SET_PROFILE_OWNER_DIALOG_TITLE, + SET_PROFILE_OWNER_POSTSETUP_WARNING, OTHER_OPTIONS_DISABLED_BY_ADMIN, + REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION, IT_ADMIN_POLICY_DISABLING_INFO_URL, + SHARE_REMOTE_BUGREPORT_DIALOG_TITLE, SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT, + SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT, SHARING_REMOTE_BUGREPORT_MESSAGE, + MANAGED_DEVICE_INFO, MANAGED_DEVICE_INFO_SUMMARY, MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME, + ENTERPRISE_PRIVACY_HEADER, INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE, + CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE, YOUR_ACCESS_TO_THIS_DEVICE_TITLE, + ADMIN_CAN_SEE_WORK_DATA_WARNING, ADMIN_CAN_SEE_APPS_WARNING, + ADMIN_CAN_SEE_USAGE_WARNING, ADMIN_CAN_SEE_NETWORK_LOGS_WARNING, + ADMIN_CAN_SEE_BUG_REPORT_WARNING, ADMIN_CAN_SEE_SECURITY_LOGS_WARNING, + ADMIN_ACTION_NONE, ADMIN_ACTION_APPS_INSTALLED, ADMIN_ACTION_APPS_COUNT_ESTIMATED, + ADMIN_ACTIONS_APPS_COUNT_MINIMUM, ADMIN_ACTION_ACCESS_LOCATION, + ADMIN_ACTION_ACCESS_MICROPHONE, ADMIN_ACTION_ACCESS_CAMERA, + ADMIN_ACTION_SET_DEFAULT_APPS, ADMIN_ACTIONS_APPS_COUNT, + ADMIN_ACTION_SET_CURRENT_INPUT_METHOD, ADMIN_ACTION_SET_INPUT_METHOD_NAME, + ADMIN_ACTION_SET_HTTP_PROXY, WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY, + WORK_PROFILE_PRIVACY_POLICY_INFO, CONNECTED_APPS_SEARCH_KEYWORDS, + WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS, ACCOUNTS_SEARCH_KEYWORDS, + CONTROLLED_BY_ADMIN_SUMMARY, WORK_PROFILE_USER_LABEL, WORK_CATEGORY_HEADER, + PERSONAL_CATEGORY_HEADER, + + // PermissionController Strings + WORK_PROFILE_DEFAULT_APPS_TITLE, HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE, + BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE, BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, + BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE, + LOCATION_AUTO_GRANTED_MESSAGE }) public @interface UpdatableStringId { } @@ -432,7 +687,8 @@ public final class DevicePolicyResources { @SystemApi public static final class Strings { - private Strings() {} + private Strings() { + } /** * An ID for any string that can't be updated. @@ -446,23 +702,1221 @@ public final class DevicePolicyResources { private static Set<String> buildStringsSet() { Set<String> strings = new HashSet<>(); + strings.addAll(Settings.buildStringsSet()); strings.addAll(Launcher.buildStringsSet()); strings.addAll(SystemUi.buildStringsSet()); strings.addAll(Core.buildStringsSet()); strings.addAll(DocumentsUi.buildStringsSet()); strings.addAll(MediaProvider.buildStringsSet()); + strings.addAll(PermissionController.buildStringsSet()); return strings; } /** * Class containing the identifiers used to update device management-related system strings + * in the Settings package + * + * @hide + */ + public static final class Settings { + + private Settings() { + } + + private static final String PREFIX = "Settings."; + + /** + * Title shown for menu item that launches face settings or enrollment, for work profile + */ + public static final String FACE_SETTINGS_FOR_WORK_TITLE = + PREFIX + "FACE_SETTINGS_FOR_WORK_TITLE"; + + /** + * Warning when removing the last fingerprint on a work profile + */ + public static final String WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE = + PREFIX + "WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE"; + + /** + * Text letting the user know that their IT admin can't reset their screen lock if they + * forget it, and they can choose to set another lock that would be specifically for + * their work apps + */ + public static final String WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK = + PREFIX + "WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK"; + + /** + * Message shown in screen lock picker for setting up a work profile screen lock + */ + public static final String WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE = + PREFIX + "WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE"; + + /** + * Title for PreferenceScreen to launch picker for security method for the managed + * profile when there is none + */ + public static final String WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE = + PREFIX + "WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE"; + + /** + * Content of the dialog shown when the user only has one attempt left to provide the + * work lock pattern before the work profile is removed + */ + public static final String WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE = + PREFIX + "WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE"; + + /** + * Content of the dialog shown when the user only has one attempt left to provide the + * work lock pattern before the work profile is removed + */ + public static final String WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE = + PREFIX + "WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE"; + + /** + * Content of the dialog shown when the user only has one attempt left to provide the + * work lock pattern before the work profile is removed + */ + public static final String WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE = + PREFIX + "WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE"; + + /** + * Content of the dialog shown when the user has failed to provide the device lock too + * many times and the device is wiped + */ + public static final String WORK_PROFILE_LOCK_ATTEMPTS_FAILED = + PREFIX + "WORK_PROFILE_LOCK_ATTEMPTS_FAILED"; + + /** + * Content description for work profile accounts group + */ + public static final String ACCESSIBILITY_CATEGORY_WORK = + PREFIX + "ACCESSIBILITY_CATEGORY_WORK"; + + /** + * Content description for personal profile accounts group + */ + public static final String ACCESSIBILITY_CATEGORY_PERSONAL = + PREFIX + "ACCESSIBILITY_CATEGORY_PERSONAL"; + + /** + * Content description for work profile details page title + */ + public static final String ACCESSIBILITY_WORK_ACCOUNT_TITLE = + PREFIX + "ACCESSIBILITY_WORK_ACCOUNT_TITLE"; + + /** + * Content description for personal profile details page title + */ + public static final String ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE = + PREFIX + "ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE"; + + /** + * Title for work profile location switch + */ + public static final String WORK_PROFILE_LOCATION_SWITCH_TITLE = + PREFIX + "WORK_PROFILE_LOCATION_SWITCH_TITLE"; + + /** + * Header when setting work profile password + */ + public static final String SET_WORK_PROFILE_PASSWORD_HEADER = + PREFIX + "SET_WORK_PROFILE_PASSWORD_HEADER"; + + /** + * Header when setting work profile PIN + */ + public static final String SET_WORK_PROFILE_PIN_HEADER = + PREFIX + "SET_WORK_PROFILE_PIN_HEADER"; + + /** + * Header when setting work profile pattern + */ + public static final String SET_WORK_PROFILE_PATTERN_HEADER = + PREFIX + "SET_WORK_PROFILE_PATTERN_HEADER"; + + /** + * Header when confirming work profile password + */ + public static final String CONFIRM_WORK_PROFILE_PASSWORD_HEADER = + PREFIX + "CONFIRM_WORK_PROFILE_PASSWORD_HEADER"; + + /** + * Header when confirming work profile pin + */ + public static final String CONFIRM_WORK_PROFILE_PIN_HEADER = + PREFIX + "CONFIRM_WORK_PROFILE_PIN_HEADER"; + + /** + * Header when confirming work profile pattern + */ + public static final String CONFIRM_WORK_PROFILE_PATTERN_HEADER = + PREFIX + "CONFIRM_WORK_PROFILE_PATTERN_HEADER"; + + /** + * Header when re-entering work profile password + */ + public static final String REENTER_WORK_PROFILE_PASSWORD_HEADER = + PREFIX + "REENTER_WORK_PROFILE_PASSWORD_HEADER"; + + /** + * Header when re-entering work profile pin + */ + public static final String REENTER_WORK_PROFILE_PIN_HEADER = + PREFIX + "REENTER_WORK_PROFILE_PIN_HEADER"; + + /** + * Message to be used to explain the users that they need to enter their work pattern to + * continue a particular operation + */ + public static final String WORK_PROFILE_CONFIRM_PATTERN = + PREFIX + "WORK_PROFILE_CONFIRM_PATTERN"; + + /** + * Message to be used to explain the users that they need to enter their work pin to + * continue a particular operation + */ + public static final String WORK_PROFILE_CONFIRM_PIN = + PREFIX + "WORK_PROFILE_CONFIRM_PIN"; + + /** + * Message to be used to explain the users that they need to enter their work password + * to + * continue a particular operation + */ + public static final String WORK_PROFILE_CONFIRM_PASSWORD = + PREFIX + "WORK_PROFILE_CONFIRM_PASSWORD"; + + /** + * This string shows = PREFIX + "shows"; up on a screen where a user can enter a pattern + * that lets them access + * their work profile. This is an extra security measure that's required for them to + * continue + */ + public static final String WORK_PROFILE_PATTERN_REQUIRED = + PREFIX + "WORK_PROFILE_PATTERN_REQUIRED"; + + /** + * This string shows = PREFIX + "shows"; up on a screen where a user can enter a pin + * that lets them access + * their work profile. This is an extra security measure that's required for them to + * continue + */ + public static final String WORK_PROFILE_PIN_REQUIRED = + PREFIX + "WORK_PROFILE_PIN_REQUIRED"; + + /** + * This string shows = PREFIX + "shows"; up on a screen where a user can enter a + * password that lets them access + * their work profile. This is an extra security measure that's required for them to + * continue + */ + public static final String WORK_PROFILE_PASSWORD_REQUIRED = + PREFIX + "WORK_PROFILE_PASSWORD_REQUIRED"; + + /** + * Header for Work Profile security settings + */ + public static final String WORK_PROFILE_SECURITY_TITLE = + PREFIX + "WORK_PROFILE_SECURITY_TITLE"; + + /** + * Header for Work Profile unify locks settings + */ + public static final String WORK_PROFILE_UNIFY_LOCKS_TITLE = + PREFIX + "WORK_PROFILE_UNIFY_LOCKS_TITLE"; + + /** + * Setting option explanation to unify work and personal locks + */ + public static final String WORK_PROFILE_UNIFY_LOCKS_SUMMARY = + PREFIX + "WORK_PROFILE_UNIFY_LOCKS_SUMMARY"; + + /** + * Further explanation when the user wants to unify work and personal locks + */ + public static final String WORK_PROFILE_UNIFY_LOCKS_DETAIL = + PREFIX + "WORK_PROFILE_UNIFY_LOCKS_DETAIL"; + + /** + * Ask if the user wants to create a new lock for personal and work as the current work + * lock is not enough for the device + */ + public static final String WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT = + PREFIX + "WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT"; + + /** + * Title of 'Work profile keyboards & tools' preference category + */ + public static final String WORK_PROFILE_KEYBOARDS_AND_TOOLS = + PREFIX + "WORK_PROFILE_KEYBOARDS_AND_TOOLS"; + + /** + * Label for state when work profile is not available + */ + public static final String WORK_PROFILE_NOT_AVAILABLE = + PREFIX + "WORK_PROFILE_NOT_AVAILABLE"; + + /** + * Label for work profile setting (to allow turning work profile on and off) + */ + public static final String WORK_PROFILE_SETTING = PREFIX + "WORK_PROFILE_SETTING"; + + /** + * Description of the work profile setting when the work profile is on + */ + public static final String WORK_PROFILE_SETTING_ON_SUMMARY = + PREFIX + "WORK_PROFILE_SETTING_ON_SUMMARY"; + + /** + * Description of the work profile setting when the work profile is off + */ + public static final String WORK_PROFILE_SETTING_OFF_SUMMARY = + PREFIX + "WORK_PROFILE_SETTING_OFF_SUMMARY"; + + /** + * Button text to remove work profile + */ + public static final String REMOVE_WORK_PROFILE = PREFIX + "REMOVE_WORK_PROFILE"; + + /** + * Text of message to show to device owner user whose administrator has installed a SSL + * CA Cert + */ + public static final String DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING = + PREFIX + "DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING"; + + /** + * Text of message to show to work profile users whose administrator has installed a SSL + * CA Cert + */ + public static final String WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING = + PREFIX + "WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING"; + + /** + * Work profile removal confirmation title + */ + public static final String WORK_PROFILE_CONFIRM_REMOVE_TITLE = + PREFIX + "WORK_PROFILE_CONFIRM_REMOVE_TITLE"; + + /** + * Work profile removal confirmation message + */ + public static final String WORK_PROFILE_CONFIRM_REMOVE_MESSAGE = + PREFIX + "WORK_PROFILE_CONFIRM_REMOVE_MESSAGE"; + + /** + * Toast shown when an app in the work profile attempts to open notification settings + * and apps in the work profile cannot access notification settings + */ + public static final String WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS = + PREFIX + "WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS"; + + /** + * Work sound settings section header + */ + public static final String WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER = + PREFIX + "WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER"; + + /** + * Title for the switch that enables syncing of personal ringtones to work profile + */ + public static final String WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE = + PREFIX + "WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE"; + + /** + * Summary for the switch that enables syncing of personal ringtones to work profile + */ + public static final String WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY = + PREFIX + "WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY"; + + /** + * Title for the option defining the work profile phone ringtone + */ + public static final String WORK_PROFILE_RINGTONE_TITLE = + PREFIX + "WORK_PROFILE_RINGTONE_TITLE"; + + /** + * Title for the option defining the default work profile notification ringtone + */ + public static final String WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE = + PREFIX + "WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE"; + + /** + * Title for the option defining the default work alarm ringtone + */ + public static final String WORK_PROFILE_ALARM_RINGTONE_TITLE = + PREFIX + "WORK_PROFILE_ALARM_RINGTONE_TITLE"; + + /** + * Summary for sounds when sync with personal sounds is active + */ + public static final String WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY = + PREFIX + "WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY"; + + /** + * Title for dialog shown when enabling sync with personal sounds + */ + public static final String + ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE = + PREFIX + "ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE"; + + /** + * Message for dialog shown when using the same sounds for work events as for personal + * events + */ + public static final String + ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE = + PREFIX + "ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE"; + + /** + * Work profile notifications section header + */ + public static final String WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER = + PREFIX + "WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER"; + + /** + * Title for the option controlling notifications for work profile + */ + public static final String WORK_PROFILE_LOCKED_NOTIFICATION_TITLE = + PREFIX + "WORK_PROFILE_LOCKED_NOTIFICATION_TITLE"; + + /** + * Title for redacting sensitive content on lockscreen for work profiles + */ + public static final String WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE = + PREFIX + "WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE"; + + /** + * Summary for redacting sensitive content on lockscreen for work profiles + */ + public static final String WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY = + PREFIX + "WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY"; + + /** + * Indicates that the work profile admin doesn't allow this notification listener to + * access + * work profile notifications + */ + public static final String WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED = + PREFIX + "WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED"; + + /** + * This setting shows a user's connected work and personal apps. + */ + public static final String CONNECTED_WORK_AND_PERSONAL_APPS_TITLE = + PREFIX + "CONNECTED_WORK_AND_PERSONAL_APPS_TITLE"; + + /** + * This text lets a user know that if they connect work and personal apps, + * they will share permissions and can access each other's data + */ + public static final String CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA = + PREFIX + "CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA"; + + /** + * This text lets a user know that they should only connect work and personal apps if + * they + * trust the work app with their personal data + */ + public static final String ONLY_CONNECT_TRUSTED_APPS = + PREFIX + "ONLY_CONNECT_TRUSTED_APPS"; + + /** + * This text lets a user know how to disconnect work and personal apps + */ + public static final String HOW_TO_DISCONNECT_APPS = PREFIX + "HOW_TO_DISCONNECT_APPS"; + + /** + * Title of confirmation dialog when connecting work and personal apps + */ + public static final String CONNECT_APPS_DIALOG_TITLE = + PREFIX + "CONNECT_APPS_DIALOG_TITLE"; + + /** + * This dialog is shown when a user tries to connect a work app to a personal + * app + */ + public static final String CONNECT_APPS_DIALOG_SUMMARY = + PREFIX + "CONNECT_APPS_DIALOG_SUMMARY"; + + /** + * This text lets the user know that their work app will be able to access data in their + * personal app + */ + public static final String APP_CAN_ACCESS_PERSONAL_DATA = + PREFIX + "APP_CAN_ACCESS_PERSONAL_DATA"; + + /** + * This text lets the user know that their work app will be able to use permissions in + * their personal app + */ + public static final String APP_CAN_ACCESS_PERSONAL_PERMISSIONS = + PREFIX + "APP_CAN_ACCESS_PERSONAL_PERMISSIONS"; + + /** + * lets a user know that they need to install an app in their work profile in order to + * connect it to the corresponding personal app + */ + public static final String INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT = + PREFIX + "INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT"; + + /** + * lets a user know that they need to install an app in their personal profile in order + * to + * connect it to the corresponding work app + */ + public static final String INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT = + PREFIX + "INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT"; + + /** + * Header for showing the organisation managing the work profile + */ + public static final String WORK_PROFILE_MANAGED_BY = PREFIX + "WORK_PROFILE_MANAGED_BY"; + + /** + * Summary showing the enterprise who manages the device or profile. + */ + public static final String MANAGED_BY = PREFIX + "MANAGED_BY"; + + /** + * Warning message about disabling usage access on profile owner + */ + public static final String WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING = + PREFIX + "WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING"; + + /** + * Title for dialog displayed when user taps a setting on their phone that's blocked by + * their IT admin + */ + public static final String DISABLED_BY_IT_ADMIN_TITLE = + PREFIX + "DISABLED_BY_IT_ADMIN_TITLE"; + + /** + * Shown when the user tries to change phone settings that are blocked by their IT admin + */ + public static final String CONTACT_YOUR_IT_ADMIN = PREFIX + "CONTACT_YOUR_IT_ADMIN"; + + /** + * warn user about policies the admin can set in a work profile + */ + public static final String WORK_PROFILE_ADMIN_POLICIES_WARNING = + PREFIX + "WORK_PROFILE_ADMIN_POLICIES_WARNING"; + + /** + * warn user about policies the admin can set on a user + */ + public static final String USER_ADMIN_POLICIES_WARNING = + PREFIX + "USER_ADMIN_POLICIES_WARNING"; + + /** + * warn user about policies the admin can set on a device + */ + public static final String DEVICE_ADMIN_POLICIES_WARNING = + PREFIX + "DEVICE_ADMIN_POLICIES_WARNING"; + + /** + * Condition that work profile is off + */ + public static final String WORK_PROFILE_OFF_CONDITION_TITLE = + PREFIX + "WORK_PROFILE_OFF_CONDITION_TITLE"; + + /** + * Title of work profile setting page + */ + public static final String MANAGED_PROFILE_SETTINGS_TITLE = + PREFIX + "MANAGED_PROFILE_SETTINGS_TITLE"; + + /** + * Setting that lets a user's personal apps identify contacts using the user's work + * directory + */ + public static final String WORK_PROFILE_CONTACT_SEARCH_TITLE = + PREFIX + "WORK_PROFILE_CONTACT_SEARCH_TITLE"; + + /** + * This setting lets a user's personal apps identify contacts using the user's work + * directory + */ + public static final String WORK_PROFILE_CONTACT_SEARCH_SUMMARY = + PREFIX + "WORK_PROFILE_CONTACT_SEARCH_SUMMARY"; + + /** + * This setting lets the user show their work events on their personal calendar + */ + public static final String CROSS_PROFILE_CALENDAR_TITLE = + PREFIX + "CROSS_PROFILE_CALENDAR_TITLE"; + + /** + * Setting description. If the user turns on this setting, they can see their work + * events on their personal calendar + */ + public static final String CROSS_PROFILE_CALENDAR_SUMMARY = + PREFIX + "CROSS_PROFILE_CALENDAR_SUMMARY"; + + /** + * Label explaining that an always-on VPN was set by the admin in the personal profile + */ + public static final String ALWAYS_ON_VPN_PERSONAL_PROFILE = + PREFIX + "ALWAYS_ON_VPN_PERSONAL_PROFILE"; + + /** + * Label explaining that an always-on VPN was set by the admin for the entire device + */ + public static final String ALWAYS_ON_VPN_DEVICE = PREFIX + "ALWAYS_ON_VPN_DEVICE"; + + /** + * Label explaining that an always-on VPN was set by the admin in the work profile + */ + public static final String ALWAYS_ON_VPN_WORK_PROFILE = + PREFIX + "ALWAYS_ON_VPN_WORK_PROFILE"; + + /** + * Label explaining that the admin installed trusted CA certificates in personal profile + */ + public static final String CA_CERTS_PERSONAL_PROFILE = + PREFIX + "CA_CERTS_PERSONAL_PROFILE"; + + /** + * Label explaining that the admin installed trusted CA certificates in work profile + */ + public static final String CA_CERTS_WORK_PROFILE = PREFIX + "CA_CERTS_WORK_PROFILE"; + + /** + * Label explaining that the admin installed trusted CA certificates for the entire + * device + */ + public static final String CA_CERTS_DEVICE = PREFIX + "CA_CERTS_DEVICE"; + + /** + * Label explaining that the admin can lock the device and change the user's password + */ + public static final String ADMIN_CAN_LOCK_DEVICE = PREFIX + "ADMIN_CAN_LOCK_DEVICE"; + + /** + * Label explaining that the admin can wipe the device remotely + */ + public static final String ADMIN_CAN_WIPE_DEVICE = PREFIX + "ADMIN_CAN_WIPE_DEVICE"; + + /** + * Label explaining that the admin configured the device to wipe itself when the + * password is mistyped too many times + */ + public static final String ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE = + PREFIX + "ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE"; + + /** + * Label explaining that the admin configured the work profile to wipe itself when the + * password is mistyped too many times + */ + public static final String ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE = + PREFIX + "ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE"; + + /** + * Message indicating that the device is enterprise-managed by a Device Owner + */ + public static final String DEVICE_MANAGED_WITHOUT_NAME = + PREFIX + "DEVICE_MANAGED_WITHOUT_NAME"; + + /** + * Message indicating that the device is enterprise-managed by a Device Owner + */ + public static final String DEVICE_MANAGED_WITH_NAME = + PREFIX + "DEVICE_MANAGED_WITH_NAME"; + + /** + * Subtext of work profile app for current setting + */ + public static final String WORK_PROFILE_APP_SUBTEXT = + PREFIX + "WORK_PROFILE_APP_SUBTEXT"; + + /** + * Subtext of personal profile app for current setting + */ + public static final String PERSONAL_PROFILE_APP_SUBTEXT = + PREFIX + "PERSONAL_PROFILE_APP_SUBTEXT"; + + /** + * Title shown for work menu item that launches fingerprint settings or enrollment + */ + public static final String FINGERPRINT_FOR_WORK = PREFIX + "FINGERPRINT_FOR_WORK"; + + /** + * Message shown in face enrollment dialog, when face unlock is disabled by device admin + */ + public static final String FACE_UNLOCK_DISABLED = PREFIX + "FACE_UNLOCK_DISABLED"; + + /** + * message shown in fingerprint enrollment dialog, when fingerprint unlock is disabled + * by device admin + */ + public static final String FINGERPRINT_UNLOCK_DISABLED = + PREFIX + "FINGERPRINT_UNLOCK_DISABLED"; + + /** + * Text shown in fingerprint settings explaining what the fingerprint can be used for in + * the case unlocking is disabled + */ + public static final String FINGERPRINT_UNLOCK_DISABLED_EXPLANATION = + PREFIX + "FINGERPRINT_UNLOCK_DISABLED_EXPLANATION"; + + /** + * Error shown when in PIN mode and PIN has been used recently + */ + public static final String PIN_RECENTLY_USED = PREFIX + "PIN_RECENTLY_USED"; + + /** + * Error shown when in PASSWORD mode and password has been used recently + */ + public static final String PASSWORD_RECENTLY_USED = PREFIX + "PASSWORD_RECENTLY_USED"; + + /** + * Title of preference to manage device admin apps + */ + public static final String MANAGE_DEVICE_ADMIN_APPS = + PREFIX + "MANAGE_DEVICE_ADMIN_APPS"; + + /** + * Inform the user that currently no device admin apps are installed and active + */ + public static final String NUMBER_OF_DEVICE_ADMINS_NONE = + PREFIX + "NUMBER_OF_DEVICE_ADMINS_NONE"; + + /** + * Inform the user how many device admin apps are installed and active + */ + public static final String NUMBER_OF_DEVICE_ADMINS = PREFIX + "NUMBER_OF_DEVICE_ADMINS"; + + /** + * Title that asks the user to contact the IT admin to reset password + */ + public static final String FORGOT_PASSWORD_TITLE = PREFIX + "FORGOT_PASSWORD_TITLE"; + + /** + * Content that asks the user to contact the IT admin to reset password + */ + public static final String FORGOT_PASSWORD_TEXT = PREFIX + "FORGOT_PASSWORD_TEXT"; + + /** + * Error message shown when trying to move device administrators to external disks, such + * as SD card + */ + public static final String ERROR_MOVE_DEVICE_ADMIN = PREFIX + "ERROR_MOVE_DEVICE_ADMIN"; + + /** + * Device admin app settings title + */ + public static final String DEVICE_ADMIN_SETTINGS_TITLE = + PREFIX + "DEVICE_ADMIN_SETTINGS_TITLE"; + + /** + * Button to remove the active device admin app + */ + public static final String REMOVE_DEVICE_ADMIN = PREFIX + "REMOVE_DEVICE_ADMIN"; + + /** + * Button to uninstall the device admin app + */ + public static final String UNINSTALL_DEVICE_ADMIN = PREFIX + "UNINSTALL_DEVICE_ADMIN"; + + /** + * Button to deactivate and uninstall the device admin app + */ + public static final String REMOVE_AND_UNINSTALL_DEVICE_ADMIN = + PREFIX + "REMOVE_AND_UNINSTALL_DEVICE_ADMIN"; + + /** + * Title for selecting device admin apps + */ + public static final String SELECT_DEVICE_ADMIN_APPS = + PREFIX + "SELECT_DEVICE_ADMIN_APPS"; + + /** + * Message when there are no available device admin apps to display + */ + public static final String NO_DEVICE_ADMINS = PREFIX + "NO_DEVICE_ADMINS"; + + /** + * Title for screen to add a device admin app + */ + public static final String ACTIVATE_DEVICE_ADMIN_APP = + PREFIX + "ACTIVATE_DEVICE_ADMIN_APP"; + + /** + * Label for button to set the active device admin + */ + public static final String ACTIVATE_THIS_DEVICE_ADMIN_APP = + PREFIX + "ACTIVATE_THIS_DEVICE_ADMIN_APP"; + + /** + * Activate a specific device admin app title + */ + public static final String ACTIVATE_DEVICE_ADMIN_APP_TITLE = + PREFIX + "ACTIVATE_DEVICE_ADMIN_APP_TITLE"; + + /** + * Device admin warning message about policies a not active admin can use + */ + public static final String NEW_DEVICE_ADMIN_WARNING = + PREFIX + "NEW_DEVICE_ADMIN_WARNING"; + + /** + * Simplified device admin warning message + */ + public static final String NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED = + PREFIX + "NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED"; + + /** + * Device admin warning message about policies the active admin can use + */ + public static final String ACTIVE_DEVICE_ADMIN_WARNING = + PREFIX + "ACTIVE_DEVICE_ADMIN_WARNING"; + + /** + * Title for screen to set a profile owner + */ + public static final String SET_PROFILE_OWNER_TITLE = PREFIX + "SET_PROFILE_OWNER_TITLE"; + + /** + * Simplified title for dialog to set a profile owner + */ + public static final String SET_PROFILE_OWNER_DIALOG_TITLE = + PREFIX + "SET_PROFILE_OWNER_DIALOG_TITLE"; + + /** + * Warning when trying to add a profile owner admin after setup has completed + */ + public static final String SET_PROFILE_OWNER_POSTSETUP_WARNING = + PREFIX + "SET_PROFILE_OWNER_POSTSETUP_WARNING"; + + /** + * Message displayed to let the user know that some of the options are disabled by admin + */ + public static final String OTHER_OPTIONS_DISABLED_BY_ADMIN = + PREFIX + "OTHER_OPTIONS_DISABLED_BY_ADMIN"; + + /** + * This is shown if the authenticator for a given account fails to remove it due to + * admin restrictions + */ + public static final String REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION = + PREFIX + "REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION"; + + /** + * Url for learning more about IT admin policy disabling + */ + public static final String IT_ADMIN_POLICY_DISABLING_INFO_URL = + PREFIX + "IT_ADMIN_POLICY_DISABLING_INFO_URL"; + + /** + * Title of dialog shown to ask for user consent for sharing a bugreport that was + * requested + * remotely by the IT administrator + */ + public static final String SHARE_REMOTE_BUGREPORT_DIALOG_TITLE = + PREFIX + "SHARE_REMOTE_BUGREPORT_DIALOG_TITLE"; + + /** + * Message of a dialog shown to ask for user consent for sharing a bugreport that was + * requested remotely by the IT administrator + */ + public static final String SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT = + PREFIX + "SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT"; + + /** + * Message of a dialog shown to ask for user consent for sharing a bugreport that was + * requested remotely by the IT administrator and it's still being taken + */ + public static final String SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT = + PREFIX + "SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT"; + + /** + * Message of a dialog shown to inform that the remote bugreport that was requested + * remotely by the IT administrator is still being taken and will be shared when + * finished + */ + public static final String SHARING_REMOTE_BUGREPORT_MESSAGE = + PREFIX + "SHARING_REMOTE_BUGREPORT_MESSAGE"; + + /** + * Managed device information screen title + */ + public static final String MANAGED_DEVICE_INFO = PREFIX + "MANAGED_DEVICE_INFO"; + + /** + * Summary for managed device info section + */ + public static final String MANAGED_DEVICE_INFO_SUMMARY = + PREFIX + "MANAGED_DEVICE_INFO_SUMMARY"; + + /** + * Summary for managed device info section including organization name + */ + public static final String MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME = + PREFIX + "MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME"; + + /** + * Enterprise Privacy settings header, summarizing the powers that the admin has + */ + public static final String ENTERPRISE_PRIVACY_HEADER = + PREFIX + "ENTERPRISE_PRIVACY_HEADER"; + + /** + * Types of information your organization can see section title + */ + public static final String INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE = + PREFIX + "INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE"; + + /** + * Changes made by your organization's admin section title + */ + public static final String CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE = + PREFIX + "CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE"; + + /** + * Your access to this device section title + */ + public static final String YOUR_ACCESS_TO_THIS_DEVICE_TITLE = + PREFIX + "YOUR_ACCESS_TO_THIS_DEVICE_TITLE"; + + /** + * Things the admin can see: data associated with the work account + */ + public static final String ADMIN_CAN_SEE_WORK_DATA_WARNING = + PREFIX + "ADMIN_CAN_SEE_WORK_DATA_WARNING"; + + /** + * Things the admin can see: Apps installed on the device + */ + public static final String ADMIN_CAN_SEE_APPS_WARNING = + PREFIX + "ADMIN_CAN_SEE_APPS_WARNING"; + + /** + * Things the admin can see: Amount of time and data spent in each app + */ + public static final String ADMIN_CAN_SEE_USAGE_WARNING = + PREFIX + "ADMIN_CAN_SEE_USAGE_WARNING"; + + /** + * Things the admin can see: Most recent network traffic log + */ + public static final String ADMIN_CAN_SEE_NETWORK_LOGS_WARNING = + PREFIX + "ADMIN_CAN_SEE_NETWORK_LOGS_WARNING"; + /** + * Things the admin can see: Most recent bug report + */ + public static final String ADMIN_CAN_SEE_BUG_REPORT_WARNING = + PREFIX + "ADMIN_CAN_SEE_BUG_REPORT_WARNING"; + + /** + * Things the admin can see: Security logs + */ + public static final String ADMIN_CAN_SEE_SECURITY_LOGS_WARNING = + PREFIX + "ADMIN_CAN_SEE_SECURITY_LOGS_WARNING"; + + /** + * Indicate that the admin never took a given action so far (e.g. did not retrieve + * security logs or request bug reports). + */ + public static final String ADMIN_ACTION_NONE = PREFIX + "ADMIN_ACTION_NONE"; + + /** + * Indicate that the admin installed one or more apps on the device + */ + public static final String ADMIN_ACTION_APPS_INSTALLED = + PREFIX + "ADMIN_ACTION_APPS_INSTALLED"; + + /** + * Explaining that the number of apps is an estimation + */ + public static final String ADMIN_ACTION_APPS_COUNT_ESTIMATED = + PREFIX + "ADMIN_ACTION_APPS_COUNT_ESTIMATED"; + + /** + * Indicating the minimum number of apps that a label refers to + */ + public static final String ADMIN_ACTIONS_APPS_COUNT_MINIMUM = + PREFIX + "ADMIN_ACTIONS_APPS_COUNT_MINIMUM"; + + /** + * Indicate that the admin granted one or more apps access to the device's location + */ + public static final String ADMIN_ACTION_ACCESS_LOCATION = + PREFIX + "ADMIN_ACTION_ACCESS_LOCATION"; + + /** + * Indicate that the admin granted one or more apps access to the microphone + */ + public static final String ADMIN_ACTION_ACCESS_MICROPHONE = + PREFIX + "ADMIN_ACTION_ACCESS_MICROPHONE"; + + /** + * Indicate that the admin granted one or more apps access to the camera + */ + public static final String ADMIN_ACTION_ACCESS_CAMERA = + PREFIX + "ADMIN_ACTION_ACCESS_CAMERA"; + + /** + * Indicate that the admin set one or more apps as defaults for common actions + */ + public static final String ADMIN_ACTION_SET_DEFAULT_APPS = + PREFIX + "ADMIN_ACTION_SET_DEFAULT_APPS"; + + /** + * Indicate the number of apps that a label refers to + */ + public static final String ADMIN_ACTIONS_APPS_COUNT = + PREFIX + "ADMIN_ACTIONS_APPS_COUNT"; + + /** + * Indicate that the current input method was set by the admin + */ + public static final String ADMIN_ACTION_SET_CURRENT_INPUT_METHOD = + PREFIX + "ADMIN_ACTION_SET_CURRENT_INPUT_METHOD"; + + /** + * The input method set by the admin + */ + public static final String ADMIN_ACTION_SET_INPUT_METHOD_NAME = + PREFIX + "ADMIN_ACTION_SET_INPUT_METHOD_NAME"; + + /** + * Indicate that a global HTTP proxy was set by the admin + */ + public static final String ADMIN_ACTION_SET_HTTP_PROXY = + PREFIX + "ADMIN_ACTION_SET_HTTP_PROXY"; + + /** + * Summary for Enterprise Privacy settings, explaining what the user can expect to find + * under it + */ + public static final String WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY = + PREFIX + "WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY"; + + /** + * Setting on privacy settings screen that will show work policy info + */ + public static final String WORK_PROFILE_PRIVACY_POLICY_INFO = + PREFIX + "WORK_PROFILE_PRIVACY_POLICY_INFO"; + + /** + * Search keywords for connected work and personal apps + */ + public static final String CONNECTED_APPS_SEARCH_KEYWORDS = + PREFIX + "CONNECTED_APPS_SEARCH_KEYWORDS"; + + /** + * Work profile unification keywords + */ + public static final String WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS = + PREFIX + "WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS"; + + /** + * Accounts keywords + */ + public static final String ACCOUNTS_SEARCH_KEYWORDS = + PREFIX + "ACCOUNTS_SEARCH_KEYWORDS"; + + /** + * Summary for settings preference disabled by administrator + */ + public static final String CONTROLLED_BY_ADMIN_SUMMARY = + PREFIX + "CONTROLLED_BY_ADMIN_SUMMARY"; + + /** + * User label for a work profile + */ + public static final String WORK_PROFILE_USER_LABEL = PREFIX + "WORK_PROFILE_USER_LABEL"; + + /** + * Header for items under the work user + */ + public static final String WORK_CATEGORY_HEADER = PREFIX + "WORK_CATEGORY_HEADER"; + + /** + * Header for items under the personal user + */ + public static final String PERSONAL_CATEGORY_HEADER = PREFIX + "category_personal"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(FACE_SETTINGS_FOR_WORK_TITLE); + strings.add(WORK_PROFILE_FINGERPRINT_LAST_DELETE_MESSAGE); + strings.add(WORK_PROFILE_IT_ADMIN_CANT_RESET_SCREEN_LOCK); + strings.add(WORK_PROFILE_SCREEN_LOCK_SETUP_MESSAGE); + strings.add(WORK_PROFILE_SET_UNLOCK_LAUNCH_PICKER_TITLE); + strings.add(WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE); + strings.add(WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE); + strings.add(WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE); + strings.add(WORK_PROFILE_LOCK_ATTEMPTS_FAILED); + strings.add(ACCESSIBILITY_CATEGORY_WORK); + strings.add(ACCESSIBILITY_CATEGORY_PERSONAL); + strings.add(ACCESSIBILITY_WORK_ACCOUNT_TITLE); + strings.add(ACCESSIBILITY_PERSONAL_ACCOUNT_TITLE); + strings.add(WORK_PROFILE_LOCATION_SWITCH_TITLE); + strings.add(SET_WORK_PROFILE_PASSWORD_HEADER); + strings.add(SET_WORK_PROFILE_PIN_HEADER); + strings.add(SET_WORK_PROFILE_PATTERN_HEADER); + strings.add(CONFIRM_WORK_PROFILE_PASSWORD_HEADER); + strings.add(CONFIRM_WORK_PROFILE_PIN_HEADER); + strings.add(CONFIRM_WORK_PROFILE_PATTERN_HEADER); + strings.add(REENTER_WORK_PROFILE_PASSWORD_HEADER); + strings.add(REENTER_WORK_PROFILE_PIN_HEADER); + strings.add(WORK_PROFILE_CONFIRM_PATTERN); + strings.add(WORK_PROFILE_CONFIRM_PIN); + strings.add(WORK_PROFILE_PASSWORD_REQUIRED); + strings.add(WORK_PROFILE_SECURITY_TITLE); + strings.add(WORK_PROFILE_UNIFY_LOCKS_TITLE); + strings.add(WORK_PROFILE_UNIFY_LOCKS_SUMMARY); + strings.add(WORK_PROFILE_UNIFY_LOCKS_DETAIL); + strings.add(WORK_PROFILE_UNIFY_LOCKS_NONCOMPLIANT); + strings.add(WORK_PROFILE_KEYBOARDS_AND_TOOLS); + strings.add(WORK_PROFILE_NOT_AVAILABLE); + strings.add(WORK_PROFILE_SETTING); + strings.add(WORK_PROFILE_SETTING_ON_SUMMARY); + strings.add(WORK_PROFILE_SETTING_OFF_SUMMARY); + strings.add(REMOVE_WORK_PROFILE); + strings.add(DEVICE_OWNER_INSTALLED_CERTIFICATE_AUTHORITY_WARNING); + strings.add(WORK_PROFILE_INSTALLED_CERTIFICATE_AUTHORITY_WARNING); + strings.add(WORK_PROFILE_CONFIRM_REMOVE_TITLE); + strings.add(WORK_PROFILE_CONFIRM_REMOVE_MESSAGE); + strings.add(WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS); + strings.add(WORK_PROFILE_SOUND_SETTINGS_SECTION_HEADER); + strings.add(WORK_PROFILE_USE_PERSONAL_SOUNDS_TITLE); + strings.add(WORK_PROFILE_USE_PERSONAL_SOUNDS_SUMMARY); + strings.add(WORK_PROFILE_RINGTONE_TITLE); + strings.add(WORK_PROFILE_NOTIFICATION_RINGTONE_TITLE); + strings.add(WORK_PROFILE_ALARM_RINGTONE_TITLE); + strings.add(WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_ACTIVE_SUMMARY); + strings.add(ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_TITLE); + strings.add(ENABLE_WORK_PROFILE_SYNC_WITH_PERSONAL_SOUNDS_DIALOG_MESSAGE); + strings.add(WORK_PROFILE_NOTIFICATIONS_SECTION_HEADER); + strings.add(WORK_PROFILE_LOCKED_NOTIFICATION_TITLE); + strings.add(WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_TITLE); + strings.add(WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY); + strings.add(WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED); + strings.add(CONNECTED_WORK_AND_PERSONAL_APPS_TITLE); + strings.add(CONNECTED_APPS_SHARE_PERMISSIONS_AND_DATA); + strings.add(ONLY_CONNECT_TRUSTED_APPS); + strings.add(HOW_TO_DISCONNECT_APPS); + strings.add(CONNECT_APPS_DIALOG_TITLE); + strings.add(CONNECT_APPS_DIALOG_SUMMARY); + strings.add(APP_CAN_ACCESS_PERSONAL_DATA); + strings.add(APP_CAN_ACCESS_PERSONAL_PERMISSIONS); + strings.add(INSTALL_IN_WORK_PROFILE_TO_CONNECT_PROMPT); + strings.add(INSTALL_IN_PERSONAL_PROFILE_TO_CONNECT_PROMPT); + strings.add(WORK_PROFILE_MANAGED_BY); + strings.add(MANAGED_BY); + strings.add(WORK_PROFILE_DISABLE_USAGE_ACCESS_WARNING); + strings.add(DISABLED_BY_IT_ADMIN_TITLE); + strings.add(CONTACT_YOUR_IT_ADMIN); + strings.add(WORK_PROFILE_ADMIN_POLICIES_WARNING); + strings.add(USER_ADMIN_POLICIES_WARNING); + strings.add(DEVICE_ADMIN_POLICIES_WARNING); + strings.add(WORK_PROFILE_OFF_CONDITION_TITLE); + strings.add(MANAGED_PROFILE_SETTINGS_TITLE); + strings.add(WORK_PROFILE_CONTACT_SEARCH_TITLE); + strings.add(WORK_PROFILE_CONTACT_SEARCH_SUMMARY); + strings.add(CROSS_PROFILE_CALENDAR_TITLE); + strings.add(CROSS_PROFILE_CALENDAR_SUMMARY); + strings.add(ALWAYS_ON_VPN_PERSONAL_PROFILE); + strings.add(ALWAYS_ON_VPN_DEVICE); + strings.add(ALWAYS_ON_VPN_WORK_PROFILE); + strings.add(CA_CERTS_PERSONAL_PROFILE); + strings.add(CA_CERTS_WORK_PROFILE); + strings.add(CA_CERTS_DEVICE); + strings.add(ADMIN_CAN_LOCK_DEVICE); + strings.add(ADMIN_CAN_WIPE_DEVICE); + strings.add(ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_DEVICE); + strings.add(ADMIN_CONFIGURED_FAILED_PASSWORD_WIPE_WORK_PROFILE); + strings.add(DEVICE_MANAGED_WITHOUT_NAME); + strings.add(DEVICE_MANAGED_WITH_NAME); + strings.add(WORK_PROFILE_APP_SUBTEXT); + strings.add(PERSONAL_PROFILE_APP_SUBTEXT); + strings.add(FINGERPRINT_FOR_WORK); + strings.add(FACE_UNLOCK_DISABLED); + strings.add(FINGERPRINT_UNLOCK_DISABLED); + strings.add(FINGERPRINT_UNLOCK_DISABLED_EXPLANATION); + strings.add(PIN_RECENTLY_USED); + strings.add(PASSWORD_RECENTLY_USED); + strings.add(MANAGE_DEVICE_ADMIN_APPS); + strings.add(NUMBER_OF_DEVICE_ADMINS_NONE); + strings.add(NUMBER_OF_DEVICE_ADMINS); + strings.add(FORGOT_PASSWORD_TITLE); + strings.add(FORGOT_PASSWORD_TEXT); + strings.add(ERROR_MOVE_DEVICE_ADMIN); + strings.add(DEVICE_ADMIN_SETTINGS_TITLE); + strings.add(REMOVE_DEVICE_ADMIN); + strings.add(UNINSTALL_DEVICE_ADMIN); + strings.add(REMOVE_AND_UNINSTALL_DEVICE_ADMIN); + strings.add(SELECT_DEVICE_ADMIN_APPS); + strings.add(NO_DEVICE_ADMINS); + strings.add(ACTIVATE_DEVICE_ADMIN_APP); + strings.add(ACTIVATE_THIS_DEVICE_ADMIN_APP); + strings.add(ACTIVATE_DEVICE_ADMIN_APP_TITLE); + strings.add(NEW_DEVICE_ADMIN_WARNING); + strings.add(NEW_DEVICE_ADMIN_WARNING_SIMPLIFIED); + strings.add(ACTIVE_DEVICE_ADMIN_WARNING); + strings.add(SET_PROFILE_OWNER_TITLE); + strings.add(SET_PROFILE_OWNER_DIALOG_TITLE); + strings.add(SET_PROFILE_OWNER_POSTSETUP_WARNING); + strings.add(OTHER_OPTIONS_DISABLED_BY_ADMIN); + strings.add(REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION); + strings.add(IT_ADMIN_POLICY_DISABLING_INFO_URL); + strings.add(SHARE_REMOTE_BUGREPORT_DIALOG_TITLE); + strings.add(SHARE_REMOTE_BUGREPORT_FINISHED_REQUEST_CONSENT); + strings.add(SHARE_REMOTE_BUGREPORT_NOT_FINISHED_REQUEST_CONSENT); + strings.add(SHARING_REMOTE_BUGREPORT_MESSAGE); + strings.add(MANAGED_DEVICE_INFO); + strings.add(MANAGED_DEVICE_INFO_SUMMARY); + strings.add(MANAGED_DEVICE_INFO_SUMMARY_WITH_NAME); + strings.add(ENTERPRISE_PRIVACY_HEADER); + strings.add(INFORMATION_YOUR_ORGANIZATION_CAN_SEE_TITLE); + strings.add(CHANGES_MADE_BY_YOUR_ORGANIZATION_ADMIN_TITLE); + strings.add(YOUR_ACCESS_TO_THIS_DEVICE_TITLE); + strings.add(ADMIN_CAN_SEE_WORK_DATA_WARNING); + strings.add(ADMIN_CAN_SEE_APPS_WARNING); + strings.add(ADMIN_CAN_SEE_USAGE_WARNING); + strings.add(ADMIN_CAN_SEE_NETWORK_LOGS_WARNING); + strings.add(ADMIN_CAN_SEE_BUG_REPORT_WARNING); + strings.add(ADMIN_CAN_SEE_SECURITY_LOGS_WARNING); + strings.add(ADMIN_ACTION_NONE); + strings.add(ADMIN_ACTION_APPS_INSTALLED); + strings.add(ADMIN_ACTION_APPS_COUNT_ESTIMATED); + strings.add(ADMIN_ACTIONS_APPS_COUNT_MINIMUM); + strings.add(ADMIN_ACTION_ACCESS_LOCATION); + strings.add(ADMIN_ACTION_ACCESS_MICROPHONE); + strings.add(ADMIN_ACTION_ACCESS_CAMERA); + strings.add(ADMIN_ACTION_SET_DEFAULT_APPS); + strings.add(ADMIN_ACTIONS_APPS_COUNT); + strings.add(ADMIN_ACTION_SET_CURRENT_INPUT_METHOD); + strings.add(ADMIN_ACTION_SET_INPUT_METHOD_NAME); + strings.add(ADMIN_ACTION_SET_HTTP_PROXY); + strings.add(WORK_PROFILE_PRIVACY_POLICY_INFO_SUMMARY); + strings.add(WORK_PROFILE_PRIVACY_POLICY_INFO); + strings.add(CONNECTED_APPS_SEARCH_KEYWORDS); + strings.add(WORK_PROFILE_UNIFICATION_SEARCH_KEYWORDS); + strings.add(ACCOUNTS_SEARCH_KEYWORDS); + strings.add(CONTROLLED_BY_ADMIN_SUMMARY); + strings.add(WORK_PROFILE_USER_LABEL); + strings.add(WORK_CATEGORY_HEADER); + strings.add(PERSONAL_CATEGORY_HEADER); + return strings; + } + } + + /** + * Class containing the identifiers used to update device management-related system strings * in the Launcher package. * * @hide */ public static final class Launcher { - private Launcher(){} + private Launcher() { + } private static final String PREFIX = "Launcher."; @@ -576,6 +2030,7 @@ public final class DevicePolicyResources { private SystemUi() { } + private static final String PREFIX = "SystemUi."; /** @@ -649,9 +2104,9 @@ public final class DevicePolicyResources { PREFIX + "QS_MSG_NAMED_WORK_PROFILE_MONITORING"; /** - * Disclosure at the bottom of Quick Settings to indicate network activity is visible to + * Disclosure at the bottom of Quick Settings to indicate network activity is visible to * admin. - */ + */ public static final String QS_MSG_WORK_PROFILE_NETWORK = PREFIX + "QS_MSG_WORK_PROFILE_NETWORK"; @@ -1413,5 +2868,71 @@ public final class DevicePolicyResources { return strings; } } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the PermissionController module. + */ + public static final class PermissionController { + + private PermissionController() { + } + + private static final String PREFIX = "PermissionController."; + + /** + * Title for settings page to show default apps for work. + */ + public static final String WORK_PROFILE_DEFAULT_APPS_TITLE = + PREFIX + "WORK_PROFILE_DEFAULT_APPS_TITLE"; + + /** + * Summary indicating that a home role holder app is missing work profile support. + */ + public static final String HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE = + PREFIX + "HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE"; + + /** + * Summary of a permission switch in Settings when the background access is denied by an + * admin. + */ + public static final String BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE = + PREFIX + "BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE"; + + /** + * Summary of a permission switch in Settings when the background access is enabled by + * an admin. + */ + public static final String BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE = + PREFIX + "BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE"; + + /** + * Summary of a permission switch in Settings when the foreground access is enabled by + * an admin. + */ + public static final String FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE = + PREFIX + "FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE"; + + /** + * Body of the notification shown to notify the user that the location permission has + * been granted to an app, accepts app name as a param. + */ + public static final String LOCATION_AUTO_GRANTED_MESSAGE = + PREFIX + "LOCATION_AUTO_GRANTED_MESSAGE"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(WORK_PROFILE_DEFAULT_APPS_TITLE); + strings.add(HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE); + strings.add(BACKGROUND_ACCESS_DISABLED_BY_ADMIN_MESSAGE); + strings.add(BACKGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE); + strings.add(FOREGROUND_ACCESS_ENABLED_BY_ADMIN_MESSAGE); + strings.add(LOCATION_AUTO_GRANTED_MESSAGE); + return strings; + } + } } } diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java index 8c232c012f50..1f7ae4ad35de 100644 --- a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java +++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java @@ -25,6 +25,7 @@ import android.annotation.SystemApi; import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; +import android.os.PersistableBundle; import android.stats.devicepolicy.DevicePolicyEnums; import java.util.Locale; @@ -52,6 +53,7 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { @SuppressLint("UseIcu") @Nullable private final Locale mLocale; private final boolean mDeviceOwnerCanGrantSensorsPermissions; + @NonNull private final PersistableBundle mAdminExtras; private FullyManagedDeviceProvisioningParams( @NonNull ComponentName deviceAdminComponentName, @@ -60,7 +62,8 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { @Nullable String timeZone, long localTime, @Nullable @SuppressLint("UseIcu") Locale locale, - boolean deviceOwnerCanGrantSensorsPermissions) { + boolean deviceOwnerCanGrantSensorsPermissions, + @NonNull PersistableBundle adminExtras) { this.mDeviceAdminComponentName = requireNonNull(deviceAdminComponentName); this.mOwnerName = requireNonNull(ownerName); this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled; @@ -69,6 +72,7 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { this.mLocale = locale; this.mDeviceOwnerCanGrantSensorsPermissions = deviceOwnerCanGrantSensorsPermissions; + this.mAdminExtras = adminExtras; } private FullyManagedDeviceProvisioningParams( @@ -78,14 +82,16 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { @Nullable String timeZone, long localTime, @Nullable String localeStr, - boolean deviceOwnerCanGrantSensorsPermissions) { + boolean deviceOwnerCanGrantSensorsPermissions, + @Nullable PersistableBundle adminExtras) { this(deviceAdminComponentName, ownerName, leaveAllSystemAppsEnabled, timeZone, localTime, getLocale(localeStr), - deviceOwnerCanGrantSensorsPermissions); + deviceOwnerCanGrantSensorsPermissions, + adminExtras); } @Nullable @@ -151,6 +157,15 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { } /** + * Returns a copy of the admin extras bundle. + * + * @see DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE + */ + public @NonNull PersistableBundle getAdminExtras() { + return new PersistableBundle(mAdminExtras); + } + + /** * Logs the provisioning params using {@link DevicePolicyEventLogger}. * * @hide @@ -188,6 +203,7 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { @Nullable private Locale mLocale; // Default to allowing control over sensor permission grants. boolean mDeviceOwnerCanGrantSensorsPermissions = true; + @NonNull private PersistableBundle mAdminExtras; /** * Initialize a new {@link Builder} to construct a @@ -262,6 +278,17 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { } /** + * Sets a {@link PersistableBundle} that contains admin-specific extras. + */ + @NonNull + public Builder setAdminExtras(@NonNull PersistableBundle adminExtras) { + mAdminExtras = adminExtras != null + ? new PersistableBundle(adminExtras) + : new PersistableBundle(); + return this; + } + + /** * Combines all of the attributes that have been set on this {@code Builder} * * @return a new {@link FullyManagedDeviceProvisioningParams} object. @@ -275,7 +302,8 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { mTimeZone, mLocalTime, mLocale, - mDeviceOwnerCanGrantSensorsPermissions); + mDeviceOwnerCanGrantSensorsPermissions, + mAdminExtras); } } @@ -298,6 +326,7 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { + ", mLocale=" + (mLocale == null ? "null" : mLocale) + ", mDeviceOwnerCanGrantSensorsPermissions=" + mDeviceOwnerCanGrantSensorsPermissions + + ", mAdminExtras=" + mAdminExtras + '}'; } @@ -310,6 +339,7 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { dest.writeLong(mLocalTime); dest.writeString(mLocale == null ? null : mLocale.toLanguageTag()); dest.writeBoolean(mDeviceOwnerCanGrantSensorsPermissions); + dest.writePersistableBundle(mAdminExtras); } @NonNull @@ -324,6 +354,7 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { long localtime = in.readLong(); String locale = in.readString(); boolean deviceOwnerCanGrantSensorsPermissions = in.readBoolean(); + PersistableBundle adminExtras = in.readPersistableBundle(); return new FullyManagedDeviceProvisioningParams( componentName, @@ -332,7 +363,8 @@ public final class FullyManagedDeviceProvisioningParams implements Parcelable { timeZone, localtime, locale, - deviceOwnerCanGrantSensorsPermissions); + deviceOwnerCanGrantSensorsPermissions, + adminExtras); } @Override diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.java b/core/java/android/app/admin/ManagedProfileProvisioningParams.java index ccbef7321be6..f91d60a6a9fa 100644 --- a/core/java/android/app/admin/ManagedProfileProvisioningParams.java +++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.java @@ -23,8 +23,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.ComponentName; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.PersistableBundle; import android.stats.devicepolicy.DevicePolicyEnums; /** @@ -49,7 +51,7 @@ public final class ManagedProfileProvisioningParams implements Parcelable { private final boolean mLeaveAllSystemAppsEnabled; private final boolean mOrganizationOwnedProvisioning; private final boolean mKeepAccountOnMigration; - + @NonNull private final PersistableBundle mAdminExtras; private ManagedProfileProvisioningParams( @NonNull ComponentName profileAdminComponentName, @@ -58,7 +60,8 @@ public final class ManagedProfileProvisioningParams implements Parcelable { @Nullable Account accountToMigrate, boolean leaveAllSystemAppsEnabled, boolean organizationOwnedProvisioning, - boolean keepAccountOnMigration) { + boolean keepAccountOnMigration, + @NonNull PersistableBundle adminExtras) { this.mProfileAdminComponentName = requireNonNull(profileAdminComponentName); this.mOwnerName = requireNonNull(ownerName); this.mProfileName = profileName; @@ -66,6 +69,7 @@ public final class ManagedProfileProvisioningParams implements Parcelable { this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled; this.mOrganizationOwnedProvisioning = organizationOwnedProvisioning; this.mKeepAccountOnMigration = keepAccountOnMigration; + this.mAdminExtras = adminExtras; } /** @@ -124,6 +128,15 @@ public final class ManagedProfileProvisioningParams implements Parcelable { } /** + * Returns a copy of the admin extras bundle. + * + * @see DevicePolicyManager#EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE + */ + public @NonNull PersistableBundle getAdminExtras() { + return new PersistableBundle(mAdminExtras); + } + + /** * Logs the provisioning params using {@link DevicePolicyEventLogger}. * * @hide @@ -160,6 +173,7 @@ public final class ManagedProfileProvisioningParams implements Parcelable { private boolean mLeaveAllSystemAppsEnabled; private boolean mOrganizationOwnedProvisioning; private boolean mKeepingAccountOnMigration; + @Nullable private PersistableBundle mAdminExtras; /** * Initialize a new {@link Builder) to construct a {@link ManagedProfileProvisioningParams}. @@ -235,6 +249,17 @@ public final class ManagedProfileProvisioningParams implements Parcelable { } /** + * Sets a {@link Bundle} that contains admin-specific extras. + */ + @NonNull + public Builder setAdminExtras(@NonNull PersistableBundle adminExtras) { + mAdminExtras = adminExtras != null + ? new PersistableBundle(adminExtras) + : new PersistableBundle(); + return this; + } + + /** * Combines all of the attributes that have been set on this {@code Builder}. * * @return a new {@link ManagedProfileProvisioningParams} object. @@ -248,7 +273,8 @@ public final class ManagedProfileProvisioningParams implements Parcelable { mAccountToMigrate, mLeaveAllSystemAppsEnabled, mOrganizationOwnedProvisioning, - mKeepingAccountOnMigration); + mKeepingAccountOnMigration, + mAdminExtras); } } @@ -270,6 +296,7 @@ public final class ManagedProfileProvisioningParams implements Parcelable { + ", mLeaveAllSystemAppsEnabled=" + mLeaveAllSystemAppsEnabled + ", mOrganizationOwnedProvisioning=" + mOrganizationOwnedProvisioning + ", mKeepAccountOnMigration=" + mKeepAccountOnMigration + + ", mAdminExtras=" + mAdminExtras + '}'; } @@ -282,6 +309,7 @@ public final class ManagedProfileProvisioningParams implements Parcelable { dest.writeBoolean(mLeaveAllSystemAppsEnabled); dest.writeBoolean(mOrganizationOwnedProvisioning); dest.writeBoolean(mKeepAccountOnMigration); + dest.writePersistableBundle(mAdminExtras); } public static final @NonNull Creator<ManagedProfileProvisioningParams> CREATOR = @@ -295,6 +323,7 @@ public final class ManagedProfileProvisioningParams implements Parcelable { boolean leaveAllSystemAppsEnabled = in.readBoolean(); boolean organizationOwnedProvisioning = in.readBoolean(); boolean keepAccountMigrated = in.readBoolean(); + PersistableBundle adminExtras = in.readPersistableBundle(); return new ManagedProfileProvisioningParams( componentName, @@ -303,7 +332,8 @@ public final class ManagedProfileProvisioningParams implements Parcelable { account, leaveAllSystemAppsEnabled, organizationOwnedProvisioning, - keepAccountMigrated); + keepAccountMigrated, + adminExtras); } @Override diff --git a/core/java/android/app/admin/ParcelableResource.java b/core/java/android/app/admin/ParcelableResource.java index dba362820b1d..0b1b166add40 100644 --- a/core/java/android/app/admin/ParcelableResource.java +++ b/core/java/android/app/admin/ParcelableResource.java @@ -175,7 +175,7 @@ public final class ParcelableResource implements Parcelable { * <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated * drawable was not found or could not be loaded.</p> */ - @NonNull + @Nullable public Drawable getDrawable( Context context, int density, @@ -200,7 +200,7 @@ public final class ParcelableResource implements Parcelable { * <p>Returns the default string by calling {@code defaultStringLoader} if the updated * string was not found or could not be loaded.</p> */ - @NonNull + @Nullable public String getString( Context context, @NonNull Callable<String> defaultStringLoader) { @@ -267,17 +267,11 @@ public final class ParcelableResource implements Parcelable { /** * returns the {@link Drawable} loaded from calling {@code defaultDrawableLoader}. */ - @NonNull + @Nullable public static Drawable loadDefaultDrawable(@NonNull Callable<Drawable> defaultDrawableLoader) { try { Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null"); - - Drawable drawable = defaultDrawableLoader.call(); - Objects.requireNonNull(drawable, "defaultDrawable can't be null"); - - return drawable; - } catch (NullPointerException rethrown) { - throw rethrown; + return defaultDrawableLoader.call(); } catch (Exception e) { throw new RuntimeException("Couldn't load default drawable: ", e); } @@ -286,17 +280,11 @@ public final class ParcelableResource implements Parcelable { /** * returns the {@link String} loaded from calling {@code defaultStringLoader}. */ - @NonNull + @Nullable public static String loadDefaultString(@NonNull Callable<String> defaultStringLoader) { try { Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); - - String string = defaultStringLoader.call(); - Objects.requireNonNull(string, "defaultString can't be null"); - - return string; - } catch (NullPointerException rethrown) { - throw rethrown; + return defaultStringLoader.call(); } catch (Exception e) { throw new RuntimeException("Couldn't load default string: ", e); } diff --git a/core/java/android/attention/AttentionManagerInternal.java b/core/java/android/attention/AttentionManagerInternal.java index 941e9e2ecce5..4e00da1b8c10 100644 --- a/core/java/android/attention/AttentionManagerInternal.java +++ b/core/java/android/attention/AttentionManagerInternal.java @@ -46,6 +46,25 @@ public abstract class AttentionManagerInternal { */ public abstract void cancelAttentionCheck(AttentionCallbackInternal callback); + /** + * Requests the continuous updates of proximity signal via the provided callback, + * until the given callback is unregistered. Currently, AttentionManagerService only + * anticipates one client and updates one client at a time. If a new client wants to + * onboard to receiving Proximity updates, please make a feature request to make proximity + * feature multi-client before depending on this feature. + * + * @param callback a callback that receives the proximity updates + * @return {@code true} if the registration should succeed. + */ + public abstract boolean onStartProximityUpdates(ProximityCallbackInternal callback); + + /** + * Requests to stop providing continuous updates until the callback is registered. + * + * @param callback a callback that was used in {@link #onStartProximityUpdates} + */ + public abstract void onStopProximityUpdates(ProximityCallbackInternal callback); + /** Internal interface for attention callback. */ public abstract static class AttentionCallbackInternal { /** @@ -64,4 +83,13 @@ public abstract class AttentionManagerInternal { */ public abstract void onFailure(int error); } + + /** Internal interface for proximity callback. */ + public abstract static class ProximityCallbackInternal { + /** + * @param distance the estimated distance of the user (in meter) + * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive. + */ + public abstract void onProximityUpdate(double distance); + } } diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index 2ddfeb4c8ab5..1d0f7c091807 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.content.ComponentName; import android.os.Parcel; @@ -106,11 +107,13 @@ public final class VirtualDeviceParams implements Parcelable { } /** - * Returns the set of activities allowed to be streamed, or {@code null} if this is not set. + * Returns the set of activities allowed to be streamed, or {@code null} if all activities are + * allowed, except the ones explicitly blocked. * * @see Builder#setAllowedActivities(Set) - * @hide // TODO(b/194949534): Unhide this API */ + // Null and empty have different semantics - Null allows all activities to be streamed + @SuppressLint("NullableCollection") @Nullable public Set<ComponentName> getAllowedActivities() { if (mAllowedActivities == null) { @@ -120,12 +123,13 @@ public final class VirtualDeviceParams implements Parcelable { } /** - * Returns the set of activities that are blocked from streaming, or {@code null} if this is not - * set. + * Returns the set of activities that are blocked from streaming, or {@code null} to indicate + * that all activities in {@link #getAllowedActivities} are allowed. * * @see Builder#setBlockedActivities(Set) - * @hide // TODO(b/194949534): Unhide this API */ + // Allowing null to enforce that at most one of allowed / blocked activities can be non-null + @SuppressLint("NullableCollection") @Nullable public Set<ComponentName> getBlockedActivities() { if (mBlockedActivities == null) { @@ -255,8 +259,10 @@ public final class VirtualDeviceParams implements Parcelable { * * @param allowedActivities A set of activity {@link ComponentName} allowed to be launched * in the virtual device. - * @hide // TODO(b/194949534): Unhide this API */ + // Null and empty have different semantics - Null allows all activities to be streamed + @SuppressLint("NullableCollection") + @NonNull public Builder setAllowedActivities(@Nullable Set<ComponentName> allowedActivities) { if (mBlockedActivities != null && allowedActivities != null) { throw new IllegalArgumentException( @@ -279,8 +285,10 @@ public final class VirtualDeviceParams implements Parcelable { * * @param blockedActivities A set of {@link ComponentName} to be blocked launching from * virtual device. - * @hide // TODO(b/194949534): Unhide this API */ + // Allowing null to enforce that at most one of allowed / blocked activities can be non-null + @SuppressLint("NullableCollection") + @NonNull public Builder setBlockedActivities(@Nullable Set<ComponentName> blockedActivities) { if (mAllowedActivities != null && blockedActivities != null) { throw new IllegalArgumentException( diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 4b4e00855ac1..a0864d6459d3 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -37,6 +37,7 @@ import android.annotation.TestApi; import android.annotation.UiContext; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.BroadcastOptions; import android.app.GameManager; import android.app.IApplicationThread; import android.app.IServiceConnection; @@ -2260,6 +2261,27 @@ public abstract class Context { } /** + * Version of {@link #sendBroadcastMultiplePermissions(Intent, String[])} that allows you to + * specify the {@link android.app.BroadcastOptions}. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param receiverPermissions Array of names of permissions that a receiver must hold + * in order to receive your broadcast. + * If empty, no permissions are required. + * @param options Additional sending options, generated from a + * {@link android.app.BroadcastOptions}. + * @see #sendBroadcastMultiplePermissions(Intent, String[]) + * @see android.app.BroadcastOptions + * @hide + */ + @SystemApi + public void sendBroadcastMultiplePermissions(@NonNull Intent intent, + @NonNull String[] receiverPermissions, @Nullable BroadcastOptions options) { + sendBroadcastMultiplePermissions(intent, receiverPermissions, options.toBundle()); + } + + /** * Broadcast the given intent to all interested BroadcastReceivers, allowing * an array of required permissions to be enforced. This call is asynchronous; it returns * immediately, and you will continue executing while the receivers are run. No results are @@ -4987,10 +5009,8 @@ public abstract class Context { * @hide * @see #getSystemService(String) */ - // TODO(216507592): Change cloudsearch_service to cloudsearch. @SystemApi - @SuppressLint("ServiceName") - public static final String CLOUDSEARCH_SERVICE = "cloudsearch_service"; + public static final String CLOUDSEARCH_SERVICE = "cloudsearch"; /** * Use with {@link #getSystemService(String)} to access the @@ -5638,6 +5658,15 @@ public abstract class Context { public static final String OVERLAY_SERVICE = "overlay"; /** + * Use with {@link #getSystemService(String)} to manage resources. + * + * @see #getSystemService(String) + * @see com.android.server.resources.ResourcesManagerService + * @hide + */ + public static final String RESOURCES_SERVICE = "resources"; + + /** * Use with {@link #getSystemService(String)} to retrieve a * {android.os.IIdmap2} for managing idmap files (used by overlay * packages). @@ -6677,21 +6706,27 @@ public abstract class Context { @NonNull Configuration overrideConfiguration); /** - * Returns a new <code>Context</code> object from the current context but with resources - * adjusted to match the metrics of <code>display</code>. Each call to this method + * Returns a new {@code Context} object from the current context but with resources + * adjusted to match the metrics of {@code display}. Each call to this method * returns a new instance of a context object. Context objects are not shared; however, * common state (such as the {@link ClassLoader} and other resources for the same - * configuration) can be shared, so the <code>Context</code> itself is lightweight. + * configuration) can be shared, so the {@code Context} itself is lightweight. + * + * <p><b>Note:</b> + * This {@code Context} is <b>not</b> expected to be updated with new configuration if the + * underlying display configuration changes and the cached {@code Resources} it returns + * could be stale. It is suggested to use + * {@link android.hardware.display.DisplayManager.DisplayListener} to listen for + * changes and re-create an instance if necessary. </p> * <p> + * This {@code Context} is <b>not</b> a UI context, do not use it to access UI components + * or obtain a {@link WindowManager} instance. + * </p><p> * To obtain an instance of {@link WindowManager} configured to show windows on the given * display, call {@link #createWindowContext(int, Bundle)} on the returned display context, * then call {@link #getSystemService(String)} or {@link #getSystemService(Class)} on the * returned window context. - * <p> - * <b>Note:</b> The context returned by <code>createDisplayContext(Display)</code> is not a UI - * context. Do not access UI components or obtain a {@link WindowManager} from the context - * created by <code>createDisplayContext(Display)</code>. - * + * </p> * @param display The display to which the current context's resources are adjusted. * * @return A context for the display. diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index fb186fd5dfb3..3e527f8d5215 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3986,7 +3986,7 @@ public class Intent implements Parcelable, Cloneable { * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast. * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @SystemApi public static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED"; @@ -6196,6 +6196,8 @@ public class Intent implements Parcelable, Cloneable { * * @hide */ + @SystemApi + @SuppressLint("ActionValue") public static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle"; @@ -7059,6 +7061,7 @@ public class Intent implements Parcelable, Cloneable { * * @hide */ + @SystemApi public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000; /** * If set, the broadcast will never go to manifest receivers in background (cached diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 1e887582e5c3..94f056110bf7 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -282,7 +282,8 @@ public class CrossProfileApps { final boolean isManagedProfile = mUserManager.isManagedProfile(userHandle.getIdentifier()); if (isManagedProfile) { - return mResources.getDrawable(R.drawable.ic_corp_badge, null); + return mContext.getPackageManager().getUserBadgeForDensityNoBackground( + userHandle, /* density= */ 0); } else { return UserIcons.getDefaultUserIcon( mResources, UserHandle.USER_SYSTEM, true /* light */); diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java index 9735f8157a57..410e106ce584 100644 --- a/core/java/android/content/pm/PackageInfoLite.java +++ b/core/java/android/content/pm/PackageInfoLite.java @@ -21,7 +21,7 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; /** * Basic information about a package as specified in its manifest. @@ -80,10 +80,10 @@ public class PackageInfoLite implements Parcelable { /** * Specifies the recommended install location. Can be one of - * {@link PackageHelper#RECOMMEND_INSTALL_INTERNAL} to install on internal storage, - * {@link PackageHelper#RECOMMEND_INSTALL_EXTERNAL} to install on external media, - * {@link PackageHelper#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors, - * or {@link PackageHelper#RECOMMEND_FAILED_INVALID_APK} for parse errors. + * {@link InstallLocationUtils#RECOMMEND_INSTALL_INTERNAL} to install on internal storage, + * {@link InstallLocationUtils#RECOMMEND_INSTALL_EXTERNAL} to install on external media, + * {@link InstallLocationUtils#RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors, + * or {@link InstallLocationUtils#RECOMMEND_FAILED_INVALID_APK} for parse errors. */ public int recommendedInstallLocation; public int installLocation; diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java index 43a4b17e5172..4c0e2e6b36ca 100644 --- a/core/java/android/content/pm/SharedLibraryInfo.java +++ b/core/java/android/content/pm/SharedLibraryInfo.java @@ -46,7 +46,7 @@ public final class SharedLibraryInfo implements Parcelable { TYPE_BUILTIN, TYPE_DYNAMIC, TYPE_STATIC, - TYPE_SDK, + TYPE_SDK_PACKAGE, }) @Retention(RetentionPolicy.SOURCE) @interface Type{} @@ -68,15 +68,21 @@ public final class SharedLibraryInfo implements Parcelable { * Shared library type: this library is <strong>not</strong> backwards * -compatible, can be updated and updates can be uninstalled. Clients * link against a specific version of the library. + * + * Static shared libraries simulate static linking while allowing for + * multiple clients to reuse the same instance of the library. */ public static final int TYPE_STATIC = 2; /** - * SDK library type: this library is <strong>not</strong> backwards - * -compatible, can be updated and updates can be uninstalled. Clients - * depend on a specific version of the library. + * SDK package shared library type: this library is <strong>not</strong> + * compatible between versions, can be updated and updates can be + * uninstalled. Clients depend on a specific version of the library. + * + * SDK packages are not loaded automatically by the OS and rely + * e.g. on 3P libraries to make them available for the clients. */ - public static final int TYPE_SDK = 3; + public static final int TYPE_SDK_PACKAGE = 3; /** * Constant for referring to an undefined version. @@ -301,7 +307,7 @@ public final class SharedLibraryInfo implements Parcelable { * @hide */ public boolean isSdk() { - return mType == TYPE_SDK; + return mType == TYPE_SDK_PACKAGE; } /** @@ -367,7 +373,7 @@ public final class SharedLibraryInfo implements Parcelable { case TYPE_STATIC: { return "static"; } - case TYPE_SDK: { + case TYPE_SDK_PACKAGE: { return "sdk"; } default: { diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index a503d14b6de4..dea083422612 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -162,5 +162,68 @@ { "name": "CtsInstallHostTestCases" } + ], + "staged-platinum-postsubmit": [ + { + "name": "CtsIncrementalInstallHostTestCases" + }, + { + "name": "CtsInstallHostTestCases" + }, + { + "name": "CtsStagedInstallHostTestCases" + }, + { + "name": "CtsExtractNativeLibsHostTestCases" + }, + { + "name": "CtsAppSecurityHostTestCases", + "options": [ + { + "include-filter": "com.android.cts.splitapp.SplitAppTest" + }, + { + "include-filter": "android.appsecurity.cts.EphemeralTest" + } + ] + }, + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.pm.PackageParserTest" + } + ] + }, + { + "name": "CtsRollbackManagerHostTestCases" + }, + { + "name": "CtsOsHostTestCases", + "options": [ + { + "include-filter": "com.android.server.pm.PackageParserTest" + } + ] + }, + { + "name": "CtsContentTestCases", + "options": [ + { + "include-filter": "android.content.cts.IntentFilterTest" + } + ] + }, + { + "name": "CtsAppEnumerationTestCases" + }, + { + "name": "PackageManagerServiceUnitTests", + "options": [ + { + "include-filter": "com.android.server.pm.test.verify.domain" + } + ] + } ] } diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 6fd2d05ad135..7a5ac8ede4a4 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -28,6 +28,7 @@ import com.android.internal.annotations.GuardedBy; import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -438,6 +439,12 @@ public final class ApkAssets { } } + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "class=" + getClass()); + pw.println(prefix + "debugName=" + getDebugName()); + pw.println(prefix + "assetPath=" + getAssetPath()); + } + private static native long nativeLoad(@FormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; private static native long nativeLoadEmpty(@PropertyFlags int flags, diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index bfd9fd0a4ef9..a05f5c927b29 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -43,6 +43,7 @@ import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.lang.ref.Reference; import java.util.ArrayList; import java.util.Arrays; @@ -1531,6 +1532,15 @@ public final class AssetManager implements AutoCloseable { } } + synchronized void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "class=" + getClass()); + pw.println(prefix + "apkAssets="); + for (int i = 0; i < mApkAssets.length; i++) { + pw.println(prefix + i); + mApkAssets[i].dump(pw, prefix + " "); + } + } + // AssetManager setup native methods. private static native long nativeCreate(); private static native void nativeDestroy(long ptr); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl b/core/java/android/content/res/IResourcesManager.aidl index 3d5998bffcbf..d1373788f1c5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl +++ b/core/java/android/content/res/IResourcesManager.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.android.systemui.shared.communal; +package android.content.res; -import android.view.SurfaceControlViewHost.SurfacePackage; +import android.os.RemoteCallback; /** -* An interface for receiving the result of a surface request. ICommunalSurfaceCallback is -* implemented by the CommunalHost (SystemUI) to process the results of a new communal surface. -*/ -interface ICommunalSurfaceCallback { - /** - * Invoked when the CommunalSurface has generated the SurfacePackage to be displayed. - */ - void onSurface(in SurfacePackage surfacePackage) = 1; + * Api for getting information about resources. + * + * {@hide} + */ +interface IResourcesManager { + boolean dumpResources(in String process, + in ParcelFileDescriptor fd, + in RemoteCallback finishCallback); }
\ No newline at end of file diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 5fd0d841f0e3..ebef0535f077 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -53,6 +53,7 @@ import android.graphics.drawable.Drawable.ConstantState; import android.graphics.drawable.DrawableInflater; import android.os.Build; import android.os.Bundle; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -78,11 +79,15 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; /** * Class for accessing an application's resources. This sits on top of the @@ -172,6 +177,11 @@ public class Resources { private int mBaseApkAssetsSize; + /** @hide */ + private static Set<Resources> sResourcesHistory = Collections.synchronizedSet( + Collections.newSetFromMap( + new WeakHashMap<>())); + /** * Returns the most appropriate default theme for the specified target SDK version. * <ul> @@ -318,6 +328,7 @@ public class Resources { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public Resources(@Nullable ClassLoader classLoader) { mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader; + sResourcesHistory.add(this); } /** @@ -2649,4 +2660,29 @@ public class Resources { } } } + + /** @hide */ + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "class=" + getClass()); + pw.println(prefix + "resourcesImpl"); + mResourcesImpl.dump(pw, prefix + " "); + } + + /** @hide */ + public static void dumpHistory(PrintWriter pw, String prefix) { + pw.println(prefix + "history"); + // Putting into a map keyed on the apk assets to deduplicate resources that are different + // objects but ultimately represent the same assets + Map<List<ApkAssets>, Resources> history = new ArrayMap<>(); + for (Resources r : sResourcesHistory) { + history.put(Arrays.asList(r.mResourcesImpl.mAssets.getApkAssets()), r); + } + int i = 0; + for (Resources r : history.values()) { + if (r != null) { + pw.println(prefix + i++); + r.dump(pw, prefix + " "); + } + } + } } diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 4d850b0ccfd5..ff072916292b 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -61,6 +61,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.util.Arrays; import java.util.Locale; @@ -1271,6 +1272,12 @@ public class ResourcesImpl { NativeAllocationRegistry.createMalloced(ResourcesImpl.class.getClassLoader(), AssetManager.getThemeFreeFunction()); + void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "class=" + getClass()); + pw.println(prefix + "assets"); + mAssets.dump(pw, prefix + " "); + } + public class ThemeImpl { /** * Unique key for the series of styles applied to this theme. diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index f13c79587a28..52bba1484f1a 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -131,11 +131,16 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * * @param name The name of the cursor window, or null if none. * @param windowSizeBytes Size of cursor window in bytes. + * @throws IllegalArgumentException if {@code windowSizeBytes} is less than 0 + * @throws AssertionError if created window pointer is 0 * <p><strong>Note:</strong> Memory is dynamically allocated as data rows are added to the * window. Depending on the amount of data stored, the actual amount of memory allocated can be * lower than specified size, but cannot exceed it. */ public CursorWindow(String name, @BytesLong long windowSizeBytes) { + if (windowSizeBytes < 0) { + throw new IllegalArgumentException("Window size cannot be less than 0"); + } mStartPos = 0; mName = name != null && name.length() != 0 ? name : "<unnamed>"; mWindowPtr = nativeCreate(mName, (int) windowSizeBytes); diff --git a/core/java/android/database/CursorWindowAllocationException.java b/core/java/android/database/CursorWindowAllocationException.java index 2e3227dc6788..5315c8b591ab 100644 --- a/core/java/android/database/CursorWindowAllocationException.java +++ b/core/java/android/database/CursorWindowAllocationException.java @@ -16,14 +16,14 @@ package android.database; +import android.annotation.NonNull; + /** * This exception is thrown when a CursorWindow couldn't be allocated, * most probably due to memory not being available. - * - * @hide */ public class CursorWindowAllocationException extends RuntimeException { - public CursorWindowAllocationException(String description) { + public CursorWindowAllocationException(@NonNull String description) { super(description); } } diff --git a/core/java/android/hardware/biometrics/IBiometricContextListener.aidl b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl new file mode 100644 index 000000000000..55cab52fc4f7 --- /dev/null +++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.biometrics; + +/** + * A secondary communication channel from AuthController back to BiometricService for + * events that are not associated with an autentication session. See + * {@link IBiometricSysuiReceiver} for events associated with a session. + * + * @hide + */ +oneway interface IBiometricContextListener { + void onDozeChanged(boolean isDozing); +} diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java index b06d076fe08e..30aa4db938da 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManager.java +++ b/core/java/android/hardware/devicestate/DeviceStateManager.java @@ -79,9 +79,9 @@ public final class DeviceStateManager { * <ul> * <li>The system deems the request can no longer be honored, for example if the requested * state becomes unsupported. - * <li>A call to {@link #cancelRequest(DeviceStateRequest)}. + * <li>A call to {@link #cancelStateRequest}. * <li>Another processes submits a request succeeding this request in which case the request - * will be suspended until the interrupting request is canceled. + * will be canceled. * </ul> * However, this behavior can be changed by setting flags on the {@link DeviceStateRequest}. * @@ -100,19 +100,18 @@ public final class DeviceStateManager { } /** - * Cancels a {@link DeviceStateRequest request} previously submitted with a call to + * Cancels the active {@link DeviceStateRequest} previously submitted with a call to * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. * <p> - * This method is noop if the {@code request} has not been submitted with a call to - * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. + * This method is noop if there is no request currently active. * * @throws SecurityException if the caller is neither the current top-focused activity nor if * the {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission is held. */ @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE, conditional = true) - public void cancelRequest(@NonNull DeviceStateRequest request) { - mGlobal.cancelRequest(request); + public void cancelStateRequest() { + mGlobal.cancelStateRequest(); } /** diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java index 85e70b0fb3e9..aba538f51043 100644 --- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java +++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java @@ -151,20 +151,14 @@ public final class DeviceStateManagerGlobal { * Cancels a {@link DeviceStateRequest request} previously submitted with a call to * {@link #requestState(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}. * - * @see DeviceStateManager#cancelRequest(DeviceStateRequest) + * @see DeviceStateManager#cancelStateRequest */ - public void cancelRequest(@NonNull DeviceStateRequest request) { + public void cancelStateRequest() { synchronized (mLock) { registerCallbackIfNeededLocked(); - final IBinder token = findRequestTokenLocked(request); - if (token == null) { - // This request has not been submitted. - return; - } - try { - mDeviceStateManager.cancelRequest(token); + mDeviceStateManager.cancelStateRequest(); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } @@ -299,20 +293,6 @@ public final class DeviceStateManagerGlobal { /** * Handles a call from the server that a request for the supplied {@code token} has become - * suspended. - */ - private void handleRequestSuspended(IBinder token) { - DeviceStateRequestWrapper request; - synchronized (mLock) { - request = mRequests.get(token); - } - if (request != null) { - request.notifyRequestSuspended(); - } - } - - /** - * Handles a call from the server that a request for the supplied {@code token} has become * canceled. */ private void handleRequestCanceled(IBinder token) { @@ -337,11 +317,6 @@ public final class DeviceStateManagerGlobal { } @Override - public void onRequestSuspended(IBinder token) { - handleRequestSuspended(token); - } - - @Override public void onRequestCanceled(IBinder token) { handleRequestCanceled(token); } @@ -395,14 +370,6 @@ public final class DeviceStateManagerGlobal { mExecutor.execute(() -> mCallback.onRequestActivated(mRequest)); } - void notifyRequestSuspended() { - if (mCallback == null) { - return; - } - - mExecutor.execute(() -> mCallback.onRequestSuspended(mRequest)); - } - void notifyRequestCanceled() { if (mCallback == null) { return; diff --git a/core/java/android/hardware/devicestate/DeviceStateRequest.java b/core/java/android/hardware/devicestate/DeviceStateRequest.java index df488d2f6df1..893d765e48da 100644 --- a/core/java/android/hardware/devicestate/DeviceStateRequest.java +++ b/core/java/android/hardware/devicestate/DeviceStateRequest.java @@ -32,8 +32,7 @@ import java.util.concurrent.Executor; * DeviceStateRequest.Callback)}. * <p> * By default, the request is kept active until a call to - * {@link DeviceStateManager#cancelRequest(DeviceStateRequest)} or until one of the following - * occurs: + * {@link DeviceStateManager#cancelStateRequest} or until one of the following occurs: * <ul> * <li>Another processes submits a request succeeding this request in which case the request * will be suspended until the interrupting request is canceled. diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl index 14ed03d09fd0..e450e42497a0 100644 --- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl +++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl @@ -41,8 +41,9 @@ interface IDeviceStateManager { * previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a * call to this method. * - * @param token the request token previously registered with - * {@link #requestState(IBinder, int, int)} + * @param token the request token provided + * @param state the state of device the request is asking for + * @param flags any flags that correspond to the request * * @throws IllegalStateException if a callback has not yet been registered for the calling * process. @@ -52,14 +53,11 @@ interface IDeviceStateManager { void requestState(IBinder token, int state, int flags); /** - * Cancels a request previously submitted with a call to + * Cancels the active request previously submitted with a call to * {@link #requestState(IBinder, int, int)}. * - * @param token the request token previously registered with - * {@link #requestState(IBinder, int, int)} - * - * @throws IllegalStateException if the supplied {@code token} has not been previously - * requested. + * @throws IllegalStateException if a callback has not yet been registered for the calling + * process. */ - void cancelRequest(IBinder token); + void cancelStateRequest(); } diff --git a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl index efb9888fb6ba..348690f65e78 100644 --- a/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl +++ b/core/java/android/hardware/devicestate/IDeviceStateManagerCallback.aidl @@ -41,16 +41,6 @@ interface IDeviceStateManagerCallback { oneway void onRequestActive(IBinder token); /** - * Called to notify the callback that a request has become suspended. Guaranteed to be called - * before a subsequent call to {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request - * becoming suspended resulted in a change of device state info. - * - * @param token the request token previously registered with - * {@link IDeviceStateManager#requestState(IBinder, int, int)} - */ - oneway void onRequestSuspended(IBinder token); - - /** * Called to notify the callback that a request has become canceled. No further callbacks will * be triggered for this request. Guaranteed to be called before a subsequent call to * {@link #onDeviceStateInfoChanged(DeviceStateInfo)} if the request becoming canceled resulted diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java index cee6a5b16a3c..b96e4f88eb93 100644 --- a/core/java/android/hardware/hdmi/HdmiClient.java +++ b/core/java/android/hardware/hdmi/HdmiClient.java @@ -21,6 +21,8 @@ import java.util.concurrent.Executor; public abstract class HdmiClient { private static final String TAG = "HdmiClient"; + private static final int UNKNOWN_VENDOR_ID = 0xFFFFFF; + /* package */ final IHdmiControlService mService; private IHdmiVendorCommandListener mIHdmiVendorCommandListener; @@ -156,11 +158,25 @@ public abstract class HdmiClient { } /** - * Sets a listener used to receive incoming vendor-specific command. + * Sets a listener used to receive incoming vendor-specific command. This listener will only + * receive {@code <Vendor Command>} but will not receive any {@code <Vendor Command with ID>} + * messages. * * @param listener listener object */ public void setVendorCommandListener(@NonNull VendorCommandListener listener) { + // Set the vendor ID to INVALID_VENDOR_ID. + setVendorCommandListener(listener, UNKNOWN_VENDOR_ID); + } + + /** + * Sets a listener used to receive incoming vendor-specific command. + * + * @param listener listener object + * @param vendorId The listener is interested in {@code <Vendor Command with ID>} received with + * this vendorId and all {@code <Vendor Command>} messages. + */ + public void setVendorCommandListener(@NonNull VendorCommandListener listener, int vendorId) { if (listener == null) { throw new IllegalArgumentException("listener cannot be null"); } @@ -169,7 +185,7 @@ public abstract class HdmiClient { } try { IHdmiVendorCommandListener wrappedListener = getListenerWrapper(listener); - mService.addVendorCommandListener(wrappedListener, getDeviceType()); + mService.addVendorCommandListener(wrappedListener, vendorId); mIHdmiVendorCommandListener = wrappedListener; } catch (RemoteException e) { Log.e(TAG, "failed to set vendor command listener: ", e); diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index 5874385f8191..ec55e121bf74 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -105,6 +105,12 @@ public final class HdmiControlManager { "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1"; /** + * Used as an extra field in the Set Menu Language intent. Contains the requested locale. + * @hide + */ + public static final String EXTRA_LOCALE = "android.hardware.hdmi.extra.LOCALE"; + + /** * Volume value for mute state. */ public static final int AVR_VOLUME_MUTED = 101; diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java index 16adee9907ed..818554dd8143 100644 --- a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java +++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java @@ -221,8 +221,8 @@ public final class HdmiControlServiceWrapper { } @Override - public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { - HdmiControlServiceWrapper.this.addVendorCommandListener(listener, deviceType); + public void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) { + HdmiControlServiceWrapper.this.addVendorCommandListener(listener, vendorId); } @Override @@ -481,7 +481,7 @@ public final class HdmiControlServiceWrapper { boolean hasVendorId) {} /** @hide */ - public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {} + public void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) {} /** @hide */ public void sendStandby(int deviceType, int deviceId) {} diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl index 6613397c8c0f..35dd9ed5bbcc 100644 --- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl +++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl @@ -76,7 +76,7 @@ interface IHdmiControlService { void askRemoteDeviceToBecomeActiveSource(int physicalAddress); void sendVendorCommand(int deviceType, int targetAddress, in byte[] params, boolean hasVendorId); - void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType); + void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId); void sendStandby(int deviceType, int deviceId); void setHdmiRecordListener(IHdmiRecordListener callback); void startOneTouchRecord(int recorderAddress, in byte[] recordSource); diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 0304815ef8fe..27403ec4fe59 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -122,9 +122,9 @@ interface IInputManager { void removePortAssociation(in String inputPort); // Add a runtime association between the input device and display. - void addUniqueIdAssociation(in String inputDeviceName, in String displayUniqueId); + void addUniqueIdAssociation(in String inputPort, in String displayUniqueId); // Remove the runtime association between the input device and display. - void removeUniqueIdAssociation(in String inputDeviceName); + void removeUniqueIdAssociation(in String inputPort); InputSensorInfo[] getSensorList(int deviceId); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index cbc837393b6b..979e9dd6a1f6 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1359,19 +1359,18 @@ public final class InputManager { } /** - * Add a runtime association between the input device name and display, by unique id. Input - * device names are expected to be unique. - * @param inputDeviceName The name of the input device. + * Add a runtime association between the input port and display, by unique id. Input ports are + * expected to be unique. + * @param inputPort The port of the input device. * @param displayUniqueId The unique id of the associated display. * <p> * Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}. * </p> * @hide */ - public void addUniqueIdAssociation(@NonNull String inputDeviceName, - @NonNull String displayUniqueId) { + public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) { try { - mIm.addUniqueIdAssociation(inputDeviceName, displayUniqueId); + mIm.addUniqueIdAssociation(inputPort, displayUniqueId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1379,15 +1378,15 @@ public final class InputManager { /** * Removes a runtime association between the input device and display. - * @param inputDeviceName The name of the input device. + * @param inputPort The port of the input device. * <p> * Requires {@link android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}. * </p> * @hide */ - public void removeUniqueIdAssociation(@NonNull String inputDeviceName) { + public void removeUniqueIdAssociation(@NonNull String inputPort) { try { - mIm.removeUniqueIdAssociation(inputDeviceName); + mIm.removeUniqueIdAssociation(inputPort); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 60f5135aed6c..7ff74c68fc53 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -1341,7 +1341,6 @@ public class UsbManager { * * @hide */ - @SystemApi @RequiresPermission(Manifest.permission.MANAGE_USB) boolean resetUsbPort(@NonNull UsbPort port, int operationId, IUsbOperationInternal callback) { diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 41642e7a9fce..af57f793bf73 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -70,6 +70,7 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_SET_INPUT_CONTEXT = 20; private static final int DO_UNSET_INPUT_CONTEXT = 30; private static final int DO_START_INPUT = 32; + private static final int DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED = 35; private static final int DO_CREATE_SESSION = 40; private static final int DO_SET_SESSION_ENABLED = 45; private static final int DO_SHOW_SOFT_INPUT = 60; @@ -175,7 +176,7 @@ class IInputMethodWrapper extends IInputMethod.Stub try { inputMethod.initializeInternal((IBinder) args.arg1, (IInputMethodPrivilegedOperations) args.arg2, msg.arg1, - (boolean) args.arg3); + (boolean) args.arg3, msg.arg2 != 0); } finally { args.recycle(); } @@ -195,14 +196,22 @@ class IInputMethodWrapper extends IInputMethod.Stub final EditorInfo info = (EditorInfo) args.arg3; final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; final boolean restarting = args.argi5 == 1; + final boolean shouldShowImeSwitcherWhenImeIsShown = args.argi6 != 0; final InputConnection ic = inputContext != null ? new RemoteInputConnection(mTarget, inputContext, cancellationGroup) : null; info.makeCompatible(mTargetSdkVersion); - inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken); + inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken, + shouldShowImeSwitcherWhenImeIsShown); args.recycle(); return; } + case DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED: { + final boolean shouldShowImeSwitcherWhenImeIsShown = msg.arg1 != 0; + inputMethod.onShouldShowImeSwitcherWhenImeIsShownChanged( + shouldShowImeSwitcherWhenImeIsShown); + return; + } case DO_CREATE_SESSION: { SomeArgs args = (SomeArgs)msg.obj; inputMethod.createSession(new InputMethodSessionCallbackWrapper( @@ -291,10 +300,11 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, - int configChanges, boolean stylusHwSupported) { - mCaller.executeOrSendMessage( - mCaller.obtainMessageIOOO( - DO_INITIALIZE_INTERNAL, configChanges, token, privOps, stylusHwSupported)); + int configChanges, boolean stylusHwSupported, + boolean shouldShowImeSwitcherWhenImeIsShown) { + mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_INITIALIZE_INTERNAL, + configChanges, shouldShowImeSwitcherWhenImeIsShown ? 1 : 0, token, privOps, + stylusHwSupported)); } @BinderThread @@ -334,13 +344,23 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void startInput(IBinder startInputToken, IInputContext inputContext, - EditorInfo attribute, boolean restarting) { + EditorInfo attribute, boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) { if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); mCancellationGroup = new CancellationGroup(); } mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken, - inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, 0 /* unused */)); + inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, + shouldShowImeSwitcherWhenImeIsShown ? 1 : 0)); + } + + @BinderThread + @Override + public void onShouldShowImeSwitcherWhenImeIsShownChanged( + boolean shouldShowImeSwitcherWhenImeIsShown) { + mCaller.executeOrSendMessage(mCaller.obtainMessageI( + DO_ON_SHOULD_SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN_CHANGED, + shouldShowImeSwitcherWhenImeIsShown ? 1 : 0)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 14f92fbb4194..fc2fbc39dbeb 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -346,7 +346,7 @@ public class InputMethodService extends AbstractInputMethodService { */ @AnyThread public static boolean canImeRenderGesturalNavButtons() { - return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, false); + return SystemProperties.getBoolean(PROP_CAN_RENDER_GESTURAL_NAV_BUTTONS, true); } /** @@ -475,7 +475,7 @@ public class InputMethodService extends AbstractInputMethodService { private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations(); @NonNull - final NavigationBarController mNavigationBarController = + private final NavigationBarController mNavigationBarController = new NavigationBarController(this); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) @@ -658,7 +658,7 @@ public class InputMethodService extends AbstractInputMethodService { @Override public final void initializeInternal(@NonNull IBinder token, IInputMethodPrivilegedOperations privilegedOperations, int configChanges, - boolean stylusHwSupported) { + boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) { if (mDestroyed) { Log.i(TAG, "The InputMethodService has already onDestroyed()." + "Ignore the initialization."); @@ -671,6 +671,8 @@ public class InputMethodService extends AbstractInputMethodService { if (stylusHwSupported) { mInkWindow = new InkWindow(mWindow.getContext()); } + mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown( + shouldShowImeSwitcherWhenImeIsShown); attachToken(token); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -780,9 +782,10 @@ public class InputMethodService extends AbstractInputMethodService { @Override public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken) { + @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) { mPrivOps.reportStartInputAsync(startInputToken); - + mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown( + shouldShowImeSwitcherWhenImeIsShown); if (restarting) { restartInput(inputConnection, editorInfo); } else { @@ -796,6 +799,18 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override + public void onShouldShowImeSwitcherWhenImeIsShownChanged( + boolean shouldShowImeSwitcherWhenImeIsShown) { + mNavigationBarController.setShouldShowImeSwitcherWhenImeIsShown( + shouldShowImeSwitcherWhenImeIsShown); + } + + /** + * {@inheritDoc} + * @hide + */ + @MainThread + @Override public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver, IBinder hideInputToken) { mSystemCallingHideSoftInput = true; @@ -1489,7 +1504,7 @@ public class InputMethodService extends AbstractInputMethodService { Context.LAYOUT_INFLATER_SERVICE); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow"); mWindow = new SoftInputWindow(this, mTheme, mDispatcherState); - + mNavigationBarController.onSoftInputWindowCreated(mWindow); { final Window window = mWindow.getWindow(); { diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index 2484cf079c56..e5c22e4de08e 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -19,6 +19,7 @@ package android.inputmethodservice; import static android.content.Intent.ACTION_OVERLAY_CHANGED; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; +import android.animation.ValueAnimator; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +29,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; @@ -44,6 +46,8 @@ import android.view.Window; import android.view.WindowInsets; import android.view.WindowInsetsController.Appearance; import android.view.WindowManagerPolicyConstants; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; import android.widget.FrameLayout; import java.util.Objects; @@ -62,6 +66,9 @@ final class NavigationBarController { @NonNull ViewTreeObserver.InternalInsetsInfo dest) { } + default void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) { + } + default void onViewInitialized() { } @@ -71,7 +78,8 @@ final class NavigationBarController { default void onDestroy() { } - default void onSystemBarAppearanceChanged(@Appearance int appearance) { + default void setShouldShowImeSwitcherWhenImeIsShown( + boolean shouldShowImeSwitcherWhenImeIsShown) { } default String toDebugString() { @@ -94,6 +102,10 @@ final class NavigationBarController { mImpl.updateTouchableInsets(originalInsets, dest); } + void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) { + mImpl.onSoftInputWindowCreated(softInputWindow); + } + void onViewInitialized() { mImpl.onViewInitialized(); } @@ -106,15 +118,21 @@ final class NavigationBarController { mImpl.onDestroy(); } - void onSystemBarAppearanceChanged(@Appearance int appearance) { - mImpl.onSystemBarAppearanceChanged(appearance); + void setShouldShowImeSwitcherWhenImeIsShown(boolean shouldShowImeSwitcherWhenImeIsShown) { + mImpl.setShouldShowImeSwitcherWhenImeIsShown(shouldShowImeSwitcherWhenImeIsShown); } String toDebugString() { return mImpl.toDebugString(); } - private static final class Impl implements Callback { + private static final class Impl implements Callback, Window.DecorCallback { + private static final int DEFAULT_COLOR_ADAPT_TRANSITION_TIME = 1700; + + // Copied from com.android.systemui.animation.Interpolators#LEGACY_DECELERATE + private static final Interpolator LEGACY_DECELERATE = + new PathInterpolator(0f, 0f, 0.2f, 1f); + @NonNull private final InputMethodService mService; @@ -130,9 +148,19 @@ final class NavigationBarController { @Nullable private BroadcastReceiver mSystemOverlayChangedReceiver; + private boolean mShouldShowImeSwitcherWhenImeIsShown; + @Appearance private int mAppearance; + @FloatRange(from = 0.0f, to = 1.0f) + private float mDarkIntensity; + + @Nullable + private ValueAnimator mTintAnimator; + + private boolean mDrawLegacyNavigationBarBackground; + Impl(@NonNull InputMethodService inputMethodService) { mService = inputMethodService; } @@ -190,7 +218,9 @@ final class NavigationBarController { // TODO(b/213337792): Support InputMethodService#setBackDisposition(). // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary. final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT - | StatusBarManager.NAVIGATION_HINT_IME_SHOWN; + | (mShouldShowImeSwitcherWhenImeIsShown + ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN + : 0); navigationBarView.setNavigationIconHints(hints); } } else { @@ -199,9 +229,14 @@ final class NavigationBarController { mLastInsets = systemInsets; } - mNavigationBarFrame.setBackground(null); + if (mDrawLegacyNavigationBarBackground) { + mNavigationBarFrame.setBackgroundColor(Color.BLACK); + } else { + mNavigationBarFrame.setBackground(null); + } - setIconTintInternal(calculateTargetDarkIntensity(mAppearance)); + setIconTintInternal(calculateTargetDarkIntensity(mAppearance, + mDrawLegacyNavigationBarBackground)); } private void uninstallNavigationBarFrameIfNecessary() { @@ -335,6 +370,13 @@ final class NavigationBarController { } @Override + public void onSoftInputWindowCreated(@NonNull SoftInputWindow softInputWindow) { + final Window window = softInputWindow.getWindow(); + mAppearance = window.getSystemBarAppearance(); + window.setDecorCallback(this); + } + + @Override public void onViewInitialized() { if (mDestroyed) { return; @@ -368,6 +410,10 @@ final class NavigationBarController { if (mDestroyed) { return; } + if (mTintAnimator != null) { + mTintAnimator.cancel(); + mTintAnimator = null; + } if (mSystemOverlayChangedReceiver != null) { mService.unregisterReceiver(mSystemOverlayChangedReceiver); mSystemOverlayChangedReceiver = null; @@ -404,6 +450,31 @@ final class NavigationBarController { } @Override + public void setShouldShowImeSwitcherWhenImeIsShown( + boolean shouldShowImeSwitcherWhenImeIsShown) { + if (mDestroyed) { + return; + } + if (mShouldShowImeSwitcherWhenImeIsShown == shouldShowImeSwitcherWhenImeIsShown) { + return; + } + mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown; + + if (mNavigationBarFrame == null) { + return; + } + final NavigationBarView navigationBarView = + mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance); + if (navigationBarView == null) { + return; + } + final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT + | (shouldShowImeSwitcherWhenImeIsShown + ? StatusBarManager.NAVIGATION_HINT_IME_SHOWN : 0); + navigationBarView.setNavigationIconHints(hints); + } + + @Override public void onSystemBarAppearanceChanged(@Appearance int appearance) { if (mDestroyed) { return; @@ -415,11 +486,26 @@ final class NavigationBarController { return; } - final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance); - setIconTintInternal(targetDarkIntensity); + final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance, + mDrawLegacyNavigationBarBackground); + + if (mTintAnimator != null) { + mTintAnimator.cancel(); + } + mTintAnimator = ValueAnimator.ofFloat(mDarkIntensity, targetDarkIntensity); + mTintAnimator.addUpdateListener( + animation -> setIconTintInternal((Float) animation.getAnimatedValue())); + mTintAnimator.setDuration(DEFAULT_COLOR_ADAPT_TRANSITION_TIME); + mTintAnimator.setStartDelay(0); + mTintAnimator.setInterpolator(LEGACY_DECELERATE); + mTintAnimator.start(); } private void setIconTintInternal(float darkIntensity) { + mDarkIntensity = darkIntensity; + if (mNavigationBarFrame == null) { + return; + } final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate(NavigationBarView.class::isInstance); if (navigationBarView == null) { @@ -429,16 +515,41 @@ final class NavigationBarController { } @FloatRange(from = 0.0f, to = 1.0f) - private static float calculateTargetDarkIntensity(@Appearance int appearance) { - final boolean lightNavBar = (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0; + private static float calculateTargetDarkIntensity(@Appearance int appearance, + boolean drawLegacyNavigationBarBackground) { + final boolean lightNavBar = !drawLegacyNavigationBarBackground + && (appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0; return lightNavBar ? 1.0f : 0.0f; } @Override + public boolean onDrawLegacyNavigationBarBackgroundChanged( + boolean drawLegacyNavigationBarBackground) { + if (mDestroyed) { + return false; + } + + if (drawLegacyNavigationBarBackground != mDrawLegacyNavigationBarBackground) { + mDrawLegacyNavigationBarBackground = drawLegacyNavigationBarBackground; + if (mDrawLegacyNavigationBarBackground) { + mNavigationBarFrame.setBackgroundColor(Color.BLACK); + } else { + mNavigationBarFrame.setBackground(null); + } + scheduleRelayout(); + onSystemBarAppearanceChanged(mAppearance); + } + return drawLegacyNavigationBarBackground; + } + + @Override public String toDebugString() { return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons + " mNavigationBarFrame=" + mNavigationBarFrame + + " mShouldShowImeSwitcherWhenImeIsShown" + mShouldShowImeSwitcherWhenImeIsShown + " mAppearance=0x" + Integer.toHexString(mAppearance) + + " mDarkIntensity=" + mDarkIntensity + + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground + "}"; } } diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index 0893d2aad740..5704dac7a327 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -31,7 +31,6 @@ import android.util.proto.ProtoOutputStream; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.WindowInsetsController; import android.view.WindowManager; import java.lang.annotation.Retention; @@ -264,11 +263,6 @@ final class SoftInputWindow extends Dialog { } } - @Override - public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) { - mService.mNavigationBarController.onSystemBarAppearanceChanged(appearance); - } - void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); mBounds.dumpDebug(proto, BOUNDS); diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index c936bfa05118..9122adfece53 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -523,9 +523,11 @@ public class NetworkPolicyManager { * * @param subId the subscriber to get the subscription plans for. * @param callingPackage the name of the package making the call. + * @return the active {@link SubscriptionPlan}s for the given subscription id, or + * {@code null} if not found. * @hide */ - @NonNull + @Nullable public SubscriptionPlan[] getSubscriptionPlans(int subId, @NonNull String callingPackage) { try { return mService.getSubscriptionPlans(subId, callingPackage); @@ -538,7 +540,7 @@ public class NetworkPolicyManager { * Get subscription plan for the given networkTemplate. * * @param template the networkTemplate to get the subscription plan for. - * @return the active {@link SubscriptionPlan} for the given template, or + * @return the active {@link SubscriptionPlan}s for the given template, or * {@code null} if not found. * @hide */ diff --git a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java index 24c22a99b78d..9772bde94ac9 100644 --- a/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java +++ b/core/java/android/net/netstats/NetworkStatsDataMigrationUtils.java @@ -16,9 +16,7 @@ package android.net.netstats; -import static android.app.usage.NetworkStatsManager.PREFIX_UID; -import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG; -import static android.app.usage.NetworkStatsManager.PREFIX_XT; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; @@ -28,6 +26,7 @@ import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.net.NetworkIdentity; import android.net.NetworkStatsCollection; import android.net.NetworkStatsHistory; @@ -54,12 +53,27 @@ import java.util.HashSet; import java.util.Set; /** - * Helper class to read old version of persistent network statistics, the implementation is - * intended to be modified by OEM partners to accommodate their custom changes. + * Helper class to read old version of persistent network statistics. + * + * The implementation is intended to be modified by OEM partners to + * accommodate their custom changes. + * * @hide */ -// @SystemApi(client = MODULE_LIBRARIES) +@SystemApi(client = MODULE_LIBRARIES) public class NetworkStatsDataMigrationUtils { + /** + * Prefix of the files which are used to store per network interface statistics. + */ + public static final String PREFIX_XT = "xt"; + /** + * Prefix of the files which are used to store per uid statistics. + */ + public static final String PREFIX_UID = "uid"; + /** + * Prefix of the files which are used to store per uid tagged traffic statistics. + */ + public static final String PREFIX_UID_TAG = "uid_tag"; private static final HashMap<String, String> sPrefixLegacyFileNameMap = new HashMap<String, String>() {{ @@ -146,17 +160,51 @@ public class NetworkStatsDataMigrationUtils { } /** - * Read legacy persisted network stats from disk. This function provides a default - * implementation to read persisted network stats from two different locations. - * And this is intended to be modified by OEM to read from custom file format or - * locations if necessary. + * Read legacy persisted network stats from disk. + * + * This function provides the implementation to read legacy network stats + * from disk. It is used for migration of legacy network stats into the + * stats provided by the Connectivity module. + * This function needs to know about the previous format(s) of the network + * stats data that might be stored on this device so it can be read and + * conserved upon upgrade to Android 13 or above. + * + * This function will be called multiple times sequentially, all on the + * same thread, and will not be called multiple times concurrently. This + * function is expected to do a substantial amount of disk access, and + * doesn't need to return particularly fast, but the first boot after + * an upgrade to Android 13+ will be held until migration is done. As + * migration is only necessary once, after the first boot following the + * upgrade, this delay is not incurred. + * + * If this function fails in any way, it should throw an exception. If this + * happens, the system can't know about the data that was stored in the + * legacy files, but it will still count data usage happening on this + * session. On the next boot, the system will try migration again, and + * merge the returned data with the data used with the previous session. + * The system will only try the migration up to three (3) times. The remaining + * count is stored in the netstats_import_legacy_file_needed device config. The + * legacy data is never deleted by the mainline module to avoid any possible + * data loss. + * + * It is possible to set the netstats_import_legacy_file_needed device config + * to any positive integer to force the module to perform the migration. This + * can be achieved by calling the following command before rebooting : + * adb shell device_config put connectivity netstats_import_legacy_file_needed 1 + * + * The AOSP implementation provides code to read persisted network stats as + * they were written by AOSP prior to Android 13. + * OEMs who have used the AOSP implementation of persisting network stats + * to disk don't need to change anything. + * OEM that had modifications to this format should modify this function + * to read from their custom file format or locations if necessary. * * @param prefix Type of data which is being read by the service. * @param bucketDuration Duration of the buckets of the object, in milliseconds. * @return {@link NetworkStatsCollection} instance. */ @NonNull - public static NetworkStatsCollection readPlatformCollectionLocked( + public static NetworkStatsCollection readPlatformCollection( @NonNull String prefix, long bucketDuration) throws IOException { final NetworkStatsCollection.Builder builder = new NetworkStatsCollection.Builder(bucketDuration); @@ -397,7 +445,7 @@ public class NetworkStatsDataMigrationUtils { if (version >= IdentitySetVersion.VERSION_ADD_OEM_MANAGED_NETWORK) { oemNetCapabilities = in.readInt(); } else { - oemNetCapabilities = NetworkIdentity.OEM_NONE; + oemNetCapabilities = NetworkTemplate.OEM_MANAGED_NO; } // Legacy files might contain TYPE_MOBILE_* types which were deprecated in later diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 2d338179186e..07a51324404c 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -34,6 +34,7 @@ import android.server.ServerProtoEnums; import android.service.batterystats.BatteryStatsServiceDumpHistoryProto; import android.service.batterystats.BatteryStatsServiceDumpProto; import android.telephony.CellSignalStrength; +import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.text.format.DateFormat; import android.util.ArrayMap; @@ -2654,6 +2655,46 @@ public abstract class BatteryStats implements Parcelable { */ public abstract Timer getPhoneDataConnectionTimer(int dataType); + /** @hide */ + public static final int RADIO_ACCESS_TECHNOLOGY_OTHER = 0; + /** @hide */ + public static final int RADIO_ACCESS_TECHNOLOGY_LTE = 1; + /** @hide */ + public static final int RADIO_ACCESS_TECHNOLOGY_NR = 2; + /** @hide */ + public static final int RADIO_ACCESS_TECHNOLOGY_COUNT = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "RADIO_ACCESS_TECHNOLOGY_", + value = {RADIO_ACCESS_TECHNOLOGY_OTHER, RADIO_ACCESS_TECHNOLOGY_LTE, + RADIO_ACCESS_TECHNOLOGY_NR}) + public @interface RadioAccessTechnology { + } + + /** @hide */ + public static final String[] RADIO_ACCESS_TECHNOLOGY_NAMES = {"Other", "LTE", "NR"}; + + /** + * Returns the time in microseconds that the mobile radio has been active on a + * given Radio Access Technology (RAT), at a given frequency (NR RAT only), for a given + * transmission power level. + * + * @param rat Radio Access Technology {@see RadioAccessTechnology} + * @param frequencyRange frequency range {@see ServiceState.FrequencyRange}, only needed for + * RADIO_ACCESS_TECHNOLOGY_NR. Use + * {@link ServiceState.FREQUENCY_RANGE_UNKNOWN} for other Radio Access + * Technologies. + * @param signalStrength the cellular signal strength. {@see CellSignalStrength#getLevel()} + * @param elapsedRealtimeMs current elapsed realtime + * @return time (in milliseconds) the mobile radio spent active in the specified state, + * while on battery. + * @hide + */ + public abstract long getActiveRadioDurationMs(@RadioAccessTechnology int rat, + @ServiceState.FrequencyRange int frequencyRange, int signalStrength, + long elapsedRealtimeMs); + static final String[] WIFI_SUPPL_STATE_NAMES = { "invalid", "disconn", "disabled", "inactive", "scanning", "authenticating", "associating", "associated", "4-way-handshake", @@ -3997,6 +4038,89 @@ public abstract class BatteryStats implements Parcelable { } } + private void printCellularPerRatBreakdown(PrintWriter pw, StringBuilder sb, String prefix, + long rawRealtimeMs) { + final String allFrequenciesHeader = + " All frequencies:\n"; + final String[] nrFrequencyRangeDescription = new String[]{ + " Unknown frequency:\n", + " Low frequency (less than 1GHz):\n", + " Middle frequency (1GHz to 3GHz):\n", + " High frequency (3GHz to 6GHz):\n", + " Mmwave frequency (greater than 6GHz):\n"}; + final String signalStrengthHeader = + " Signal Strength Time:\n"; + final String[] signalStrengthDescription = new String[]{ + " unknown: ", + " poor: ", + " moderate: ", + " good: ", + " great: "}; + + final long totalActiveTimesMs = getMobileRadioActiveTime(rawRealtimeMs * 1000, + STATS_SINCE_CHARGED) / 1000; + + sb.setLength(0); + sb.append(prefix); + sb.append("Active Cellular Radio Access Technology Breakdown:"); + pw.println(sb); + + boolean hasData = false; + final int numSignalStrength = CellSignalStrength.getNumSignalStrengthLevels(); + for (int rat = RADIO_ACCESS_TECHNOLOGY_COUNT - 1; rat >= 0; rat--) { + sb.setLength(0); + sb.append(prefix); + sb.append(" "); + sb.append(RADIO_ACCESS_TECHNOLOGY_NAMES[rat]); + sb.append(":\n"); + sb.append(prefix); + + final int numFreqLvl = + rat == RADIO_ACCESS_TECHNOLOGY_NR ? nrFrequencyRangeDescription.length : 1; + for (int freqLvl = numFreqLvl - 1; freqLvl >= 0; freqLvl--) { + final int freqDescriptionStart = sb.length(); + boolean hasFreqData = false; + if (rat == RADIO_ACCESS_TECHNOLOGY_NR) { + sb.append(nrFrequencyRangeDescription[freqLvl]); + } else { + sb.append(allFrequenciesHeader); + } + + sb.append(prefix); + sb.append(signalStrengthHeader); + for (int strength = 0; strength < numSignalStrength; strength++) { + final long timeMs = getActiveRadioDurationMs(rat, freqLvl, strength, + rawRealtimeMs); + if (timeMs <= 0) continue; + hasFreqData = true; + sb.append(prefix); + sb.append(signalStrengthDescription[strength]); + formatTimeMs(sb, timeMs); + sb.append("("); + sb.append(formatRatioLocked(timeMs, totalActiveTimesMs)); + sb.append(")\n"); + } + + if (hasFreqData) { + hasData = true; + pw.print(sb); + sb.setLength(0); + sb.append(prefix); + } else { + // No useful data was printed, rewind sb to before the start of this frequency. + sb.setLength(freqDescriptionStart); + } + } + } + + if (!hasData) { + sb.setLength(0); + sb.append(prefix); + sb.append(" (no activity)"); + pw.println(sb); + } + } + /** * Temporary for settings. */ @@ -5269,6 +5393,8 @@ public abstract class BatteryStats implements Parcelable { printControllerActivity(pw, sb, prefix, CELLULAR_CONTROLLER_NAME, getModemControllerActivity(), which); + printCellularPerRatBreakdown(pw, sb, prefix + " ", rawRealtimeMs); + pw.print(" Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes)); pw.print(" Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes)); pw.print(" Cellular packets received: "); pw.println(mobileRxTotalPackets); diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index 2a609b8f6ae4..f16bbc66e6cd 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -24,6 +24,8 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; import android.content.Context; import android.net.NetworkStack; import android.os.connectivity.CellularBatteryStats; @@ -515,6 +517,42 @@ public final class BatteryStatsManager { } /** + * Indicates that Bluetooth was toggled on. + * + * @param uid calling package uid + * @param reason why Bluetooth has been turned on + * @param packageName package responsible for this change + */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) { + try { + mBatteryStats.noteBluetoothOn(uid, reason, packageName); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Indicates that Bluetooth was toggled off. + * + * @param uid calling package uid + * @param reason why Bluetooth has been turned on + * @param packageName package responsible for this change + */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) { + try { + mBatteryStats.noteBluetoothOff(uid, reason, packageName); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Indicates that a new Bluetooth LE scan has started. * * @param ws worksource (to be used for battery blaming). diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index df5b7bc237df..4fe6524dee27 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -21,15 +21,16 @@ import android.os.BatterySaverPolicyConfig; import android.os.ParcelDuration; import android.os.PowerSaveState; import android.os.WorkSource; +import android.os.IWakeLockCallback; /** @hide */ interface IPowerManager { void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, in WorkSource ws, - String historyTag, int displayId); + String historyTag, int displayId, IWakeLockCallback callback); void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName, - int uidtoblame, int displayId); + int uidtoblame, int displayId, IWakeLockCallback callback); @UnsupportedAppUsage void releaseWakeLock(IBinder lock, int flags); void updateWakeLockUids(IBinder lock, in int[] uids); @@ -40,6 +41,7 @@ interface IPowerManager boolean setPowerModeChecked(int mode, boolean enabled); void updateWakeLockWorkSource(IBinder lock, in WorkSource ws, String historyTag); + void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback); boolean isWakeLockLevelSupported(int level); void userActivity(int displayId, long time, int event, int flags); diff --git a/core/java/android/os/IWakeLockCallback.aidl b/core/java/android/os/IWakeLockCallback.aidl new file mode 100644 index 000000000000..89615d22421e --- /dev/null +++ b/core/java/android/os/IWakeLockCallback.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * @hide + */ +oneway interface IWakeLockCallback { + oneway void onStateChanged(boolean enabled); +} diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 3bc3ec83bde5..321b3643b45b 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -2088,6 +2088,102 @@ public final class Parcel { } /** + * Flatten a homogeneous multi-dimensional array with fixed-size. This delegates to other + * APIs to write a one-dimensional array. Use {@link #readFixedArray(Object)} or + * {@link #createFixedArray(Class, int[])} with the same dimensions to unmarshal. + * + * @param val The array to be written. + * @param parcelableFlags Contextual flags as per + * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}. + * Used only if val is an array of Parcelable objects. + * @param dimensions an array of int representing length of each dimension. The array should be + * sized with the exact size of dimensions. + * + * @see #readFixedArray + * @see #createFixedArray + * @see #writeBooleanArray + * @see #writeByteArray + * @see #writeCharArray + * @see #writeIntArray + * @see #writeLongArray + * @see #writeFloatArray + * @see #writeDoubleArray + * @see #writeBinderArray + * @see #writeInterfaceArray + * @see #writeTypedArray + * @throws BadParcelableException If the array's component type is not supported or if its + * size doesn't match with the given dimensions. + */ + public <T> void writeFixedArray(@Nullable T val, int parcelableFlags, + @NonNull int... dimensions) { + if (val == null) { + writeInt(-1); + return; + } + writeFixedArrayInternal(val, parcelableFlags, /*index=*/0, dimensions); + } + + private <T> void writeFixedArrayInternal(T val, int parcelableFlags, int index, + int[] dimensions) { + if (index >= dimensions.length) { + throw new BadParcelableException("Array has more dimensions than expected: " + + dimensions.length); + } + + int length = dimensions[index]; + + // val should be an array of length N + if (val == null) { + throw new BadParcelableException("Non-null array shouldn't have a null array."); + } + if (!val.getClass().isArray()) { + throw new BadParcelableException("Not an array: " + val); + } + if (Array.getLength(val) != length) { + throw new BadParcelableException("bad length: expected " + length + ", but got " + + Array.getLength(val)); + } + + // Delegates to other writers if this is a one-dimensional array. + // Otherwise, write component arrays with recursive calls. + + final Class<?> componentType = val.getClass().getComponentType(); + if (!componentType.isArray() && index + 1 != dimensions.length) { + throw new BadParcelableException("Array has fewer dimensions than expected: " + + dimensions.length); + } + if (componentType == boolean.class) { + writeBooleanArray((boolean[]) val); + } else if (componentType == byte.class) { + writeByteArray((byte[]) val); + } else if (componentType == char.class) { + writeCharArray((char[]) val); + } else if (componentType == int.class) { + writeIntArray((int[]) val); + } else if (componentType == long.class) { + writeLongArray((long[]) val); + } else if (componentType == float.class) { + writeFloatArray((float[]) val); + } else if (componentType == double.class) { + writeDoubleArray((double[]) val); + } else if (componentType == IBinder.class) { + writeBinderArray((IBinder[]) val); + } else if (IInterface.class.isAssignableFrom(componentType)) { + writeInterfaceArray((IInterface[]) val); + } else if (Parcelable.class.isAssignableFrom(componentType)) { + writeTypedArray((Parcelable[]) val, parcelableFlags); + } else if (componentType.isArray()) { + writeInt(length); + for (int i = 0; i < length; i++) { + writeFixedArrayInternal(Array.get(val, i), parcelableFlags, index + 1, + dimensions); + } + } else { + throw new BadParcelableException("unknown type for fixed-size array: " + componentType); + } + } + + /** * Flatten a generic object in to a parcel. The given Object value may * currently be one of the following types: * @@ -3788,6 +3884,317 @@ public final class Parcel { } /** + * Read a new multi-dimensional array from a parcel. If you want to read Parcelable or + * IInterface values, use {@link #readFixedArray(Object, Parcelable.Creator)} or + * {@link #readFixedArray(Object, Function)}. + * @param val the destination array to hold the read values. + * + * @see #writeTypedArray + * @see #readBooleanArray + * @see #readByteArray + * @see #readCharArray + * @see #readIntArray + * @see #readLongArray + * @see #readFloatArray + * @see #readDoubleArray + * @see #readBinderArray + * @see #readInterfaceArray + * @see #readTypedArray + */ + public <T> void readFixedArray(@NonNull T val) { + Class<?> componentType = val.getClass().getComponentType(); + if (componentType == boolean.class) { + readBooleanArray((boolean[]) val); + } else if (componentType == byte.class) { + readByteArray((byte[]) val); + } else if (componentType == char.class) { + readCharArray((char[]) val); + } else if (componentType == int.class) { + readIntArray((int[]) val); + } else if (componentType == long.class) { + readLongArray((long[]) val); + } else if (componentType == float.class) { + readFloatArray((float[]) val); + } else if (componentType == double.class) { + readDoubleArray((double[]) val); + } else if (componentType == IBinder.class) { + readBinderArray((IBinder[]) val); + } else if (componentType.isArray()) { + int length = readInt(); + if (length != Array.getLength(val)) { + throw new BadParcelableException("Bad length: expected " + Array.getLength(val) + + ", but got " + length); + } + for (int i = 0; i < length; i++) { + readFixedArray(Array.get(val, i)); + } + } else { + throw new BadParcelableException("Unknown type for fixed-size array: " + componentType); + } + } + + /** + * Read a new multi-dimensional array of typed interfaces from a parcel. + * If you want to read Parcelable values, use + * {@link #readFixedArray(Object, Parcelable.Creator)}. For values of other types, use + * {@link #readFixedArray(Object)}. + * @param val the destination array to hold the read values. + */ + public <T, S extends IInterface> void readFixedArray(@NonNull T val, + @NonNull Function<IBinder, S> asInterface) { + Class<?> componentType = val.getClass().getComponentType(); + if (IInterface.class.isAssignableFrom(componentType)) { + readInterfaceArray((S[]) val, asInterface); + } else if (componentType.isArray()) { + int length = readInt(); + if (length != Array.getLength(val)) { + throw new BadParcelableException("Bad length: expected " + Array.getLength(val) + + ", but got " + length); + } + for (int i = 0; i < length; i++) { + readFixedArray(Array.get(val, i), asInterface); + } + } else { + throw new BadParcelableException("Unknown type for fixed-size array: " + componentType); + } + } + + /** + * Read a new multi-dimensional array of typed parcelables from a parcel. + * If you want to read IInterface values, use + * {@link #readFixedArray(Object, Function)}. For values of other types, use + * {@link #readFixedArray(Object)}. + * @param val the destination array to hold the read values. + */ + public <T, S extends Parcelable> void readFixedArray(@NonNull T val, + @NonNull Parcelable.Creator<S> c) { + Class<?> componentType = val.getClass().getComponentType(); + if (Parcelable.class.isAssignableFrom(componentType)) { + readTypedArray((S[]) val, c); + } else if (componentType.isArray()) { + int length = readInt(); + if (length != Array.getLength(val)) { + throw new BadParcelableException("Bad length: expected " + Array.getLength(val) + + ", but got " + length); + } + for (int i = 0; i < length; i++) { + readFixedArray(Array.get(val, i), c); + } + } else { + throw new BadParcelableException("Unknown type for fixed-size array: " + componentType); + } + } + + private void ensureClassHasExpectedDimensions(@NonNull Class<?> cls, int numDimension) { + if (numDimension <= 0) { + throw new BadParcelableException("Fixed-size array should have dimensions."); + } + + for (int i = 0; i < numDimension; i++) { + if (!cls.isArray()) { + throw new BadParcelableException("Array has fewer dimensions than expected: " + + numDimension); + } + cls = cls.getComponentType(); + } + if (cls.isArray()) { + throw new BadParcelableException("Array has more dimensions than expected: " + + numDimension); + } + } + + /** + * Read and return a new multi-dimensional array from a parcel. Returns null if the + * previously written array object is null. If you want to read Parcelable or + * IInterface values, use {@link #createFixedArray(Class, Parcelable.Creator, int[])} or + * {@link #createFixedArray(Class, Function, int[])}. + * @param cls the Class object for the target array type. (e.g. int[][].class) + * @param dimensions an array of int representing length of each dimension. + * + * @see #writeTypedArray + * @see #createBooleanArray + * @see #createByteArray + * @see #createCharArray + * @see #createIntArray + * @see #createLongArray + * @see #createFloatArray + * @see #createDoubleArray + * @see #createBinderArray + * @see #createInterfaceArray + * @see #createTypedArray + */ + @Nullable + public <T> T createFixedArray(@NonNull Class<T> cls, @NonNull int... dimensions) { + // Check if type matches with dimensions + // If type is one-dimensional array, delegate to other creators + // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray + + ensureClassHasExpectedDimensions(cls, dimensions.length); + + T val = null; + final Class<?> componentType = cls.getComponentType(); + if (componentType == boolean.class) { + val = (T) createBooleanArray(); + } else if (componentType == byte.class) { + val = (T) createByteArray(); + } else if (componentType == char.class) { + val = (T) createCharArray(); + } else if (componentType == int.class) { + val = (T) createIntArray(); + } else if (componentType == long.class) { + val = (T) createLongArray(); + } else if (componentType == float.class) { + val = (T) createFloatArray(); + } else if (componentType == double.class) { + val = (T) createDoubleArray(); + } else if (componentType == IBinder.class) { + val = (T) createBinderArray(); + } else if (componentType.isArray()) { + int length = readInt(); + if (length < 0) { + return null; + } + if (length != dimensions[0]) { + throw new BadParcelableException("Bad length: expected " + dimensions[0] + + ", but got " + length); + } + + // Create a multi-dimensional array with an innermost component type and dimensions + Class<?> innermost = componentType.getComponentType(); + while (innermost.isArray()) { + innermost = innermost.getComponentType(); + } + val = (T) Array.newInstance(innermost, dimensions); + for (int i = 0; i < length; i++) { + readFixedArray(Array.get(val, i)); + } + return val; + } else { + throw new BadParcelableException("Unknown type for fixed-size array: " + componentType); + } + + // Check if val is null (which is OK) or has the expected size. + // This check doesn't have to be multi-dimensional because multi-dimensional arrays + // are created with expected dimensions. + if (val != null && Array.getLength(val) != dimensions[0]) { + throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got " + + Array.getLength(val)); + } + return val; + } + + /** + * Read and return a new multi-dimensional array of typed interfaces from a parcel. + * Returns null if the previously written array object is null. If you want to read + * Parcelable values, use {@link #createFixedArray(Class, Parcelable.Creator, int[])}. + * For values of other types use {@link #createFixedArray(Class, int[])}. + * @param cls the Class object for the target array type. (e.g. IFoo[][].class) + * @param dimensions an array of int representing length of each dimension. + */ + @Nullable + public <T, S extends IInterface> T createFixedArray(@NonNull Class<T> cls, + @NonNull Function<IBinder, S> asInterface, @NonNull int... dimensions) { + // Check if type matches with dimensions + // If type is one-dimensional array, delegate to other creators + // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray + + ensureClassHasExpectedDimensions(cls, dimensions.length); + + T val = null; + final Class<?> componentType = cls.getComponentType(); + if (IInterface.class.isAssignableFrom(componentType)) { + val = (T) createInterfaceArray(n -> (S[]) Array.newInstance(componentType, n), + asInterface); + } else if (componentType.isArray()) { + int length = readInt(); + if (length < 0) { + return null; + } + if (length != dimensions[0]) { + throw new BadParcelableException("Bad length: expected " + dimensions[0] + + ", but got " + length); + } + + // Create a multi-dimensional array with an innermost component type and dimensions + Class<?> innermost = componentType.getComponentType(); + while (innermost.isArray()) { + innermost = innermost.getComponentType(); + } + val = (T) Array.newInstance(innermost, dimensions); + for (int i = 0; i < length; i++) { + readFixedArray(Array.get(val, i), asInterface); + } + return val; + } else { + throw new BadParcelableException("Unknown type for fixed-size array: " + componentType); + } + + // Check if val is null (which is OK) or has the expected size. + // This check doesn't have to be multi-dimensional because multi-dimensional arrays + // are created with expected dimensions. + if (val != null && Array.getLength(val) != dimensions[0]) { + throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got " + + Array.getLength(val)); + } + return val; + } + + /** + * Read and return a new multi-dimensional array of typed parcelables from a parcel. + * Returns null if the previously written array object is null. If you want to read + * IInterface values, use {@link #createFixedArray(Class, Function, int[])}. + * For values of other types use {@link #createFixedArray(Class, int[])}. + * @param cls the Class object for the target array type. (e.g. Foo[][].class) + * @param dimensions an array of int representing length of each dimension. + */ + @Nullable + public <T, S extends Parcelable> T createFixedArray(@NonNull Class<T> cls, + @NonNull Parcelable.Creator<S> c, @NonNull int... dimensions) { + // Check if type matches with dimensions + // If type is one-dimensional array, delegate to other creators + // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray + + ensureClassHasExpectedDimensions(cls, dimensions.length); + + T val = null; + final Class<?> componentType = cls.getComponentType(); + if (Parcelable.class.isAssignableFrom(componentType)) { + val = (T) createTypedArray(c); + } else if (componentType.isArray()) { + int length = readInt(); + if (length < 0) { + return null; + } + if (length != dimensions[0]) { + throw new BadParcelableException("Bad length: expected " + dimensions[0] + + ", but got " + length); + } + + // Create a multi-dimensional array with an innermost component type and dimensions + Class<?> innermost = componentType.getComponentType(); + while (innermost.isArray()) { + innermost = innermost.getComponentType(); + } + val = (T) Array.newInstance(innermost, dimensions); + for (int i = 0; i < length; i++) { + readFixedArray(Array.get(val, i), c); + } + return val; + } else { + throw new BadParcelableException("Unknown type for fixed-size array: " + componentType); + } + + // Check if val is null (which is OK) or has the expected size. + // This check doesn't have to be multi-dimensional because multi-dimensional arrays + // are created with expected dimensions. + if (val != null && Array.getLength(val) != dimensions[0]) { + throw new BadParcelableException("Bad length: expected " + dimensions[0] + ", but got " + + Array.getLength(val)); + } + return val; + } + + /** * Write a heterogeneous array of Parcelable objects into the Parcel. * Each object in the array is written along with its class name, so * that the correct class can later be instantiated. As a result, this diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 5bd8588d8970..315eef78724e 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -2770,6 +2770,23 @@ public final class PowerManager { public static final int PRE_IDLE_TIMEOUT_MODE_SHORT = 2; /** + * A listener interface to get notified when the wakelock is enabled/disabled. + */ + public interface WakeLockStateListener { + /** + * Frameworks could disable the wakelock because either device's power allowlist has + * changed, or the app's wakelock has exceeded its quota, or the app goes into cached + * state. + * <p> + * This callback is called whenever the wakelock's state has changed. + * </p> + * + * @param enabled true is enabled, false is disabled. + */ + void onStateChanged(boolean enabled); + } + + /** * A wake lock is a mechanism to indicate that your application needs * to have the device stay on. * <p> @@ -2800,6 +2817,8 @@ public final class PowerManager { private String mHistoryTag; private final String mTraceName; private final int mDisplayId; + private WakeLockStateListener mListener; + private IWakeLockCallback mCallback; private final Runnable mReleaser = () -> release(RELEASE_FLAG_TIMEOUT); @@ -2890,7 +2909,7 @@ public final class PowerManager { Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0); try { mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource, - mHistoryTag, mDisplayId); + mHistoryTag, mDisplayId, mCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3083,6 +3102,45 @@ public final class PowerManager { } }; } + + /** + * Set the listener to get notified when the wakelock is enabled/disabled. + * + * @param executor {@link Executor} to handle listener callback. + * @param listener listener to be added, set the listener to null to cancel a listener. + */ + public void setStateListener(@NonNull @CallbackExecutor Executor executor, + @Nullable WakeLockStateListener listener) { + Preconditions.checkNotNull(executor, "executor cannot be null"); + synchronized (mToken) { + if (listener != mListener) { + mListener = listener; + if (listener != null) { + mCallback = new IWakeLockCallback.Stub() { + public void onStateChanged(boolean enabled) { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> { + listener.onStateChanged(enabled); + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + } else { + mCallback = null; + } + if (mHeld) { + try { + mService.updateWakeLockCallback(mToken, mCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + } } /** diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 49c052097492..d223a19b4348 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -106,20 +106,28 @@ public final class VibrationAttributes implements Parcelable { public static final int USAGE_NOTIFICATION = 0x30 | USAGE_CLASS_ALARM; /** * Usage value to use for vibrations which mean a request to enter/end a - * communication, such as a VoIP communication or video-conference. + * communication with the user, such as a voice prompt. */ public static final int USAGE_COMMUNICATION_REQUEST = 0x40 | USAGE_CLASS_ALARM; /** * Usage value to use for touch vibrations. + * + * <p>Most typical haptic feedback should be classed as <em>touch</em> feedback. Examples + * include vibrations for tap, long press, drag and scroll. */ public static final int USAGE_TOUCH = 0x10 | USAGE_CLASS_FEEDBACK; /** - * Usage value to use for vibrations which emulate physical effects, such as edge squeeze. + * Usage value to use for vibrations which emulate physical hardware reactions, + * such as edge squeeze. + * + * <p>Note that normal screen-touch feedback "click" effects would typically be + * classed as {@link #USAGE_TOUCH}, and that on-screen "physical" animations + * like bouncing would be {@link #USAGE_MEDIA}. */ public static final int USAGE_PHYSICAL_EMULATION = 0x20 | USAGE_CLASS_FEEDBACK; /** - * Usage value to use for vibrations which provide a feedback for hardware interaction, - * such as a fingerprint sensor. + * Usage value to use for vibrations which provide a feedback for hardware + * component interaction, such as a fingerprint sensor. */ public static final int USAGE_HARDWARE_FEEDBACK = 0x30 | USAGE_CLASS_FEEDBACK; /** @@ -183,7 +191,6 @@ public final class VibrationAttributes implements Parcelable { /** * Return the vibration usage class. - * @return USAGE_CLASS_ALARM, USAGE_CLASS_FEEDBACK or USAGE_CLASS_UNKNOWN */ @UsageClass public int getUsageClass() { @@ -192,7 +199,6 @@ public final class VibrationAttributes implements Parcelable { /** * Return the vibration usage. - * @return one of the values that can be set in {@link Builder#setUsage(int)} */ @Usage public int getUsage() { @@ -429,16 +435,8 @@ public final class VibrationAttributes implements Parcelable { } /** - * Sets the attribute describing the type of corresponding vibration. - * @param usage one of {@link VibrationAttributes#USAGE_ALARM}, - * {@link VibrationAttributes#USAGE_RINGTONE}, - * {@link VibrationAttributes#USAGE_NOTIFICATION}, - * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST}, - * {@link VibrationAttributes#USAGE_TOUCH}, - * {@link VibrationAttributes#USAGE_PHYSICAL_EMULATION}, - * {@link VibrationAttributes#USAGE_HARDWARE_FEEDBACK}. - * {@link VibrationAttributes#USAGE_ACCESSIBILITY}. - * {@link VibrationAttributes#USAGE_MEDIA}. + * Sets the attribute describing the type of the corresponding vibration. + * @param usage The type of usage for the vibration * @return the same Builder instance. */ public @NonNull Builder setUsage(@Usage int usage) { diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index f490587ae1e1..21c64876c24c 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -194,27 +194,27 @@ public abstract class VibrationEffect implements Parcelable { } /** - * Create a waveform vibration. + * Create a waveform vibration, using only off/on transitions at the provided time intervals, + * and potentially repeating. * - * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For - * each pair, the value in the amplitude array determines the strength of the vibration and the - * value in the timing array determines how long it vibrates for. An amplitude of 0 implies no - * vibration (i.e. off), and any pairs with a timing value of 0 will be ignored. + * <p>In effect, the timings array represents the number of milliseconds <em>before</em> turning + * the vibrator on, followed by the number of milliseconds to keep the vibrator on, then + * the number of milliseconds turned off, and so on. Consequently, the first timing value will + * often be 0, so that the effect will start vibrating immediately. * - * <p>The amplitude array of the generated waveform will be the same size as the given - * timing array with alternating values of 0 (i.e. off) and {@link #DEFAULT_AMPLITUDE}, - * starting with 0. Therefore the first timing value will be the period to wait before turning - * the vibrator on, the second value will be how long to vibrate at {@link #DEFAULT_AMPLITUDE} - * strength, etc. + * <p>This method is equivalent to calling {@link #createWaveform(long[], int[], int)} with + * corresponding amplitude values alternating between 0 and {@link #DEFAULT_AMPLITUDE}, + * beginning with 0. * * <p>To cause the pattern to repeat, pass the index into the timings array at which to start * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely * and should be cancelled via {@link Vibrator#cancel()}. * - * @param timings The pattern of alternating on-off timings, starting with off. Timing values - * of 0 will cause the timing / amplitude pair to be ignored. - * @param repeat The index into the timings array at which to repeat, or -1 if you you don't - * want to repeat. + * @param timings The pattern of alternating on-off timings, starting with an 'off' timing, and + * representing the length of time to sustain the individual item (not + * cumulative). + * @param repeat The index into the timings array at which to repeat, or -1 if you don't + * want to repeat indefinitely. * * @return The desired effect. */ @@ -229,11 +229,10 @@ public abstract class VibrationEffect implements Parcelable { /** * Create a waveform vibration. * - * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs. For - * each pair, the value in the amplitude array determines the strength of the vibration and the - * value in the timing array determines how long it vibrates for, in milliseconds. Amplitude - * values must be between 0 and 255, and an amplitude of 0 implies no vibration (i.e. off). Any - * pairs with a timing value of 0 will be ignored. + * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs, + * provided in separate arrays. For each pair, the value in the amplitude array determines + * the strength of the vibration and the value in the timing array determines how long it + * vibrates for, in milliseconds. * * <p>To cause the pattern to repeat, pass the index into the timings array at which to start * the repetition, or -1 to disable repeating. Repeating effects will be played indefinitely @@ -244,8 +243,8 @@ public abstract class VibrationEffect implements Parcelable { * @param amplitudes The amplitude values of the timing / amplitude pairs. Amplitude values * must be between 0 and 255, or equal to {@link #DEFAULT_AMPLITUDE}. An * amplitude value of 0 implies the motor is off. - * @param repeat The index into the timings array at which to repeat, or -1 if you you don't - * want to repeat. + * @param repeat The index into the timings array at which to repeat, or -1 if you don't + * want to repeat indefinitely. * * @return The desired effect. */ @@ -411,9 +410,9 @@ public abstract class VibrationEffect implements Parcelable { * * <p>The waveform will start the first transition from the vibrator off state, with the * resonant frequency by default. To provide an initial state, use - * {@link #startWaveform(VibrationParameter)}. + * {@link #startWaveform(VibrationEffect.VibrationParameter)}. * - * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. + * @see VibrationEffect.WaveformBuilder */ @NonNull public static WaveformBuilder startWaveform() { @@ -422,14 +421,16 @@ public abstract class VibrationEffect implements Parcelable { /** * Start building a waveform vibration with an initial state specified by a - * {@link VibrationParameter}. + * {@link VibrationEffect.VibrationParameter}. * * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing * control over vibration amplitude and frequency via smooth transitions between values. * - * @param initialParameter The initial {@link VibrationParameter} value to be applied at the - * beginning of the vibration. + * @param initialParameter The initial {@link VibrationEffect.VibrationParameter} value to be + * applied at the beginning of the vibration. * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. + * + * @see VibrationEffect.WaveformBuilder */ @NonNull public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter) { @@ -440,17 +441,19 @@ public abstract class VibrationEffect implements Parcelable { /** * Start building a waveform vibration with an initial state specified by two - * {@link VibrationParameter VibrationParameters}. + * {@link VibrationEffect.VibrationParameter VibrationParameters}. * * <p>The waveform builder offers more flexibility for creating waveform vibrations, allowing * control over vibration amplitude and frequency via smooth transitions between values. * - * @param initialParameter1 The initial {@link VibrationParameter} value to be applied at the - * beginning of the vibration. - * @param initialParameter2 The initial {@link VibrationParameter} value to be applied at the - * beginning of the vibration, must be a different type of parameter - * than the one specified by the first argument. + * @param initialParameter1 The initial {@link VibrationEffect.VibrationParameter} value to be + * applied at the beginning of the vibration. + * @param initialParameter2 The initial {@link VibrationEffect.VibrationParameter} value to be + * applied at the beginning of the vibration, must be a different type + * of parameter than the one specified by the first argument. * @return The {@link VibrationEffect.WaveformBuilder} started with the initial parameters. + * + * @see VibrationEffect.WaveformBuilder */ @NonNull public static WaveformBuilder startWaveform(@NonNull VibrationParameter initialParameter1, @@ -806,7 +809,46 @@ public abstract class VibrationEffect implements Parcelable { } /** - * A composition of haptic primitives that, when combined, create a single haptic effect. + * A composition of haptic elements that are combined to be playable as a single + * {@link VibrationEffect}. + * + * <p>The haptic primitives are available as {@code Composition.PRIMITIVE_*} constants and + * can be added to a composition to create a custom vibration effect. Here is an example of an + * effect that grows in intensity and then dies off, with a longer rising portion for emphasis + * and an extra tick 100ms after: + * + * <code> + * VibrationEffect effect = VibrationEffect.startComposition() + * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f) + * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f) + * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100) + * .compose(); + * </code> + * + * <p>Composition elements can also be {@link VibrationEffect} instances, including other + * compositions, and off durations, which are periods of time when the vibrator will be + * turned off. Here is an example of a composition that "warms up" with a light tap, + * a stronger double tap, then repeats a vibration pattern indefinitely: + * + * <code> + * VibrationEffect repeatingEffect = VibrationEffect.startComposition() + * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK) + * .addOffDuration(Duration.ofMillis(10)) + * .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK)) + * .addOffDuration(Duration.ofMillis(50)) + * .addEffect(VibrationEffect.createWaveform(pattern, repeatIndex)) + * .compose(); + * </code> + * + * <p>When choosing to play a composed effect, you should check that individual components are + * supported by the device by using the appropriate vibrator method: + * + * <ul> + * <li>Primitive support can be checked using {@link Vibrator#arePrimitivesSupported}. + * <li>Effect support can be checked using {@link Vibrator#areEffectsSupported}. + * <li>Amplitude control for one-shot and waveforms with amplitude values can be checked + * using {@link Vibrator#hasAmplitudeControl}. + * </ul> * * @see VibrationEffect#startComposition() */ @@ -1092,16 +1134,77 @@ public abstract class VibrationEffect implements Parcelable { * A builder for waveform haptic effects. * * <p>Waveform vibrations constitute of one or more timed transitions to new sets of vibration - * parameters. These parameters can be the vibration amplitude or frequency, for example. + * parameters. These parameters can be the vibration amplitude, frequency, or both. + * + * <p>The following example ramps a vibrator turned off to full amplitude at 120Hz, over 100ms + * starting at 60Hz, then holds that state for 200ms and ramps back down again over 100ms: + * + * <code> + * import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; + * import static android.os.VibrationEffect.VibrationParameter.targetFrequency; + * + * VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60)) + * .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) + * .addSustain(Duration.ofMillis(200)) + * .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) + * .build(); + * </code> + * + * <p>The initial state of the waveform can be set via + * {@link VibrationEffect#startWaveform(VibrationParameter)} or + * {@link VibrationEffect#startWaveform(VibrationParameter, VibrationParameter)}. If the initial + * parameters are not set then the {@link WaveformBuilder} will start with the vibrator off, + * represented by zero amplitude, at the vibrator's resonant frequency. + * + * <p>Repeating waveforms can be created by building the repeating block separately and adding + * it to the end of a composition with + * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}: * * <p>Note that physical vibration actuators have different reaction times for changing * amplitude and frequency. Durations specified here represent a timeline for the target * parameters, and quality of effects may be improved if the durations allow time for a * transition to be smoothly applied. * - * <p>Repeating waveforms can be built by constructing the repeating block separately and adding - * it to the end of a composition using - * {@link Composition#repeatEffectIndefinitely(VibrationEffect)}. + * <p>The following example illustrates both an initial state and a repeating section, using + * a {@link VibrationEffect.Composition}. The resulting effect will have a tick followed by a + * repeated beating effect with a rise that stretches out and a sharp finish. + * + * <code> + * VibrationEffect patternToBeRepeated = VibrationEffect.startWaveform(targetAmplitude(0.2f)) + * .addSustain(Duration.ofMillis(10)) + * .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f)) + * .addSustain(Duration.ofMillis(30)) + * .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f)) + * .addSustain(Duration.ofMillis(50)) + * .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f)) + * .build(); + * + * VibrationEffect effect = VibrationEffect.startComposition() + * .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) + * .addOffDuration(Duration.ofMillis(20)) + * .repeatEffectIndefinitely(patternToBeRepeated) + * .compose(); + * </code> + * + * <p>The amplitude step waveforms that can be created via + * {@link VibrationEffect#createWaveform(long[], int[], int)} can also be created with + * {@link WaveformBuilder} by adding zero duration transitions: + * + * <code> + * // These two effects are the same + * VibrationEffect waveform = VibrationEffect.createWaveform( + * new long[] { 10, 20, 30 }, // timings in milliseconds + * new int[] { 51, 102, 204 }, // amplitudes in [0,255] + * -1); // repeat index + * + * VibrationEffect sameWaveform = VibrationEffect.startWaveform(targetAmplitude(0.2f)) + * .addSustain(Duration.ofMillis(10)) + * .addTransition(Duration.ZERO, targetAmplitude(0.4f)) + * .addSustain(Duration.ofMillis(20)) + * .addTransition(Duration.ZERO, targetAmplitude(0.8f)) + * .addSustain(Duration.ofMillis(30)) + * .build(); + * </code> * * @see VibrationEffect#startWaveform */ diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 23baa5d70c66..8f5086021530 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -98,7 +98,8 @@ public abstract class Vibrator { /** * Vibration effect support: unsupported * - * This effect is <b>not</b> supported by the underlying hardware. + * This effect is <b>not</b> natively supported by the underlying hardware, although + * the system may still play a fallback vibration. */ public static final int VIBRATION_EFFECT_SUPPORT_NO = 2; @@ -485,20 +486,25 @@ public abstract class Vibrator { String reason, @NonNull VibrationAttributes attributes); /** - * Query whether the vibrator supports the given effects. + * Query whether the vibrator natively supports the given effects. * - * Not all hardware reports its effect capabilities, so the system may not necessarily know - * whether an effect is supported or not. + * <p>If an effect is not supported, the system may still automatically fall back to playing + * a simpler vibration instead, which is not optimised for the specific device. This includes + * the unknown case, which can't be determined in advance, that will dynamically attempt to + * fall back if the optimised effect fails to play. * - * The returned array will be the same length as the query array and the value at a given index - * will contain {@link #VIBRATION_EFFECT_SUPPORT_YES} if the effect at that same index in the - * querying array is supported, {@link #VIBRATION_EFFECT_SUPPORT_NO} if it isn't supported, or - * {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether it's - * supported or not. + * <p>The returned array will be the same length as the query array and the value at a given + * index will contain {@link #VIBRATION_EFFECT_SUPPORT_YES} if the effect at that same index + * in the querying array is supported, {@link #VIBRATION_EFFECT_SUPPORT_NO} if it isn't + * supported, or {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN} if the system can't determine whether + * it's supported or not, as some hardware doesn't report its effect capabilities. + * + * <p>Use {@link #areAllEffectsSupported(int...)} to get a single combined result, + * or for convenience when querying exactly one effect. * * @param effectIds Which effects to query for. * @return An array containing the systems current knowledge about whether the given effects - * are supported or not. + * are natively supported by the device, or not. */ @NonNull @VibrationEffectSupport @@ -515,23 +521,27 @@ public abstract class Vibrator { /** * Query whether the vibrator supports all of the given effects. * - * Not all hardware reports its effect capabilities, so the system may not necessarily know - * whether an effect is supported or not. + * <p>If an effect is not supported, the system may still automatically fall back to a simpler + * vibration instead, which is not optimised for the specific device, however vibration isn't + * guaranteed in this case. * - * If the result is {@link #VIBRATION_EFFECT_SUPPORT_YES}, all effects in the query are + * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_YES}, all effects in the query are * supported by the hardware. * - * If the result is {@link #VIBRATION_EFFECT_SUPPORT_NO}, at least one of the effects in the - * query is not supported. + * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_NO}, at least one of the effects in the + * query is not supported, and using them may fall back to an un-optimized vibration or no + * vibration. * - * If the result is {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN}, the system doesn't know whether - * all of the effects are supported. It may support any or all of the queried effects, + * <p>If the result is {@link #VIBRATION_EFFECT_SUPPORT_UNKNOWN}, the system doesn't know + * whether all of the effects are supported. It may support any or all of the queried effects, * but there's no way to programmatically know whether a {@link #vibrate} call will successfully * cause a vibration. It's guaranteed, however, that none of the queried effects are * definitively unsupported by the hardware. * + * <p>Use {@link #areEffectsSupported(int...)} to get individual results for each effect. + * * @param effectIds Which effects to query for. - * @return Whether all of the effects are supported. + * @return Whether all of the effects are natively supported by the device. */ @VibrationEffectSupport public final int areAllEffectsSupported( @@ -555,6 +565,12 @@ public abstract class Vibrator { * will contain whether the effect at that same index in the querying array is supported or * not. * + * <p>If a primitive is not supported by the device, then <em>no vibration</em> will occur if + * it is played. + * + * <p>Use {@link #areAllPrimitivesSupported(int...)} to get a single combined result, + * or for convenience when querying exactly one primitive. + * * @param primitiveIds Which primitives to query for. * @return Whether the primitives are supported. */ @@ -572,8 +588,13 @@ public abstract class Vibrator { /** * Query whether the vibrator supports all of the given primitives. * + * <p>If a primitive is not supported by the device, then <em>no vibration</em> will occur if + * it is played. + * + * <p>Use {@link #arePrimitivesSupported(int...)} to get individual results for each primitive. + * * @param primitiveIds Which primitives to query for. - * @return Whether primitives effects are supported. + * @return Whether all specified primitives are supported. */ public final boolean areAllPrimitivesSupported( @NonNull @VibrationEffect.Composition.PrimitiveType int... primitiveIds) { diff --git a/core/java/android/os/logcat/ILogcatManagerService.aidl b/core/java/android/os/logcat/ILogcatManagerService.aidl index 68b5679919d6..02db2749bbe8 100644 --- a/core/java/android/os/logcat/ILogcatManagerService.aidl +++ b/core/java/android/os/logcat/ILogcatManagerService.aidl @@ -22,5 +22,7 @@ package android.os.logcat; interface ILogcatManagerService { void startThread(in int uid, in int gid, in int pid, in int fd); void finishThread(in int uid, in int gid, in int pid, in int fd); + void approve(in int uid, in int gid, in int pid, in int fd); + void decline(in int uid, in int gid, in int pid, in int fd); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 14055ac19e5b..ee6f9c033271 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2436,6 +2436,23 @@ public final class Settings { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_MMS_MESSAGE_SETTING = "android.settings.MMS_MESSAGE_SETTING"; + /** + * Activity Action: Show a screen of bedtime settings, which is provided by the wellbeing app. + * <p> + * The handler of this intent action may not exist. + * <p> + * To start an activity with this intent, apps should set the wellbeing package explicitly in + * the intent together with this action. The wellbeing package is defined in + * {@code com.android.internal.R.string.config_defaultWellbeingPackage}. + * <p> + * Output: Nothing + * + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_BEDTIME_SETTINGS = "android.settings.BEDTIME_SETTINGS"; + // End of Intent actions for Settings /** @@ -6077,9 +6094,11 @@ public final class Settings { } /** @hide */ - @UnsupportedAppUsage - public static String getStringForUser(ContentResolver resolver, String name, - int userHandle) { + @SystemApi + @Nullable + @SuppressLint("VisiblySynchronized") + public static String getStringForUser(@NonNull ContentResolver resolver, + @NonNull String name, int userHandle) { if (MOVED_TO_GLOBAL.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure" + " to android.provider.Settings.Global."); @@ -6311,8 +6330,9 @@ public final class Settings { } /** @hide */ - @UnsupportedAppUsage - public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) { + @SystemApi + public static int getIntForUser(@NonNull ContentResolver cr, @NonNull String name, + int def, int userHandle) { String v = getStringForUser(cr, name, userHandle); return parseIntSettingWithDefault(v, def); } @@ -10048,6 +10068,13 @@ public final class Settings { public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles"; /** + * The duration of timeout, in milliseconds, to switch from a non-primary user to the + * primary user when the device is docked. + * @hide + */ + public static final String TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero"; + + /** * Backup manager behavioral parameters. * This is encoded as a key=value list, separated by commas. Ex: * @@ -10281,6 +10308,15 @@ public final class Settings { public static final String NEARBY_SHARING_SLICE_URI = "nearby_sharing_slice_uri"; /** + * Current provider of Fast Pair saved devices page. + * Default value in @string/config_defaultNearbyFastPairSettingsDevicesComponent. + * No VALIDATOR as this setting will not be backed up. + * @hide + */ + public static final String NEARBY_FAST_PAIR_SETTINGS_DEVICES_COMPONENT = + "nearby_fast_pair_settings_devices_component"; + + /** * Controls whether aware is enabled. * @hide */ @@ -10632,6 +10668,15 @@ public final class Settings { public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled"; /** + * Setting to store denylisted system languages by the CEC {@code <Set Menu Language>} + * confirmation dialog. + * + * @hide + */ + public static final String HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST = + "hdmi_cec_set_menu_language_denylist"; + + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. */ diff --git a/core/java/android/service/attention/AttentionService.java b/core/java/android/service/attention/AttentionService.java index 49ab5db74b87..f5c59b597c3a 100644 --- a/core/java/android/service/attention/AttentionService.java +++ b/core/java/android/service/attention/AttentionService.java @@ -24,11 +24,14 @@ import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; +import android.util.Slog; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.util.Objects; /** @@ -51,6 +54,7 @@ import java.lang.annotation.RetentionPolicy; */ @SystemApi public abstract class AttentionService extends Service { + private static final String LOG_TAG = "AttentionService"; /** * The {@link Intent} that must be declared as handled by the service. To be supported, the * service must also require the {@link android.Manifest.permission#BIND_ATTENTION_SERVICE} @@ -80,6 +84,9 @@ public abstract class AttentionService extends Service { /** Camera permission is not granted. */ public static final int ATTENTION_FAILURE_CAMERA_PERMISSION_ABSENT = 6; + /** Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported). */ + public static final double PROXIMITY_UNKNOWN = -1; + /** * Result codes for when attention check was successful. * @@ -118,6 +125,20 @@ public abstract class AttentionService extends Service { Preconditions.checkNotNull(callback); AttentionService.this.onCancelAttentionCheck(new AttentionCallback(callback)); } + + /** {@inheritDoc} */ + @Override + public void onStartProximityUpdates(IProximityCallback callback) { + Objects.requireNonNull(callback); + AttentionService.this.onStartProximityUpdates(new ProximityCallback(callback)); + + } + + /** {@inheritDoc} */ + @Override + public void onStopProximityUpdates() { + AttentionService.this.onStopProximityUpdates(); + } }; @Nullable @@ -143,6 +164,23 @@ public abstract class AttentionService extends Service { */ public abstract void onCancelAttentionCheck(@NonNull AttentionCallback callback); + /** + * Requests the continuous updates of proximity signal via the provided callback, + * until the given callback is unregistered. + * + * @param callback the callback to return the result to + */ + public void onStartProximityUpdates(@NonNull ProximityCallback callback) { + Slog.w(LOG_TAG, "Override this method."); + } + + /** + * Requests to stop providing continuous updates until the callback is registered. + */ + public void onStopProximityUpdates() { + Slog.w(LOG_TAG, "Override this method."); + } + /** Callbacks for AttentionService results. */ public static final class AttentionCallback { @NonNull private final IAttentionCallback mCallback; @@ -174,4 +212,26 @@ public abstract class AttentionService extends Service { } } } + + /** Callbacks for ProximityCallback results. */ + public static final class ProximityCallback { + @NonNull private final WeakReference<IProximityCallback> mCallback; + + private ProximityCallback(@NonNull IProximityCallback callback) { + mCallback = new WeakReference<>(callback); + } + + /** + * @param distance the estimated distance of the user (in meter) + * The distance will be PROXIMITY_UNKNOWN if the proximity sensing was inconclusive. + * + */ + public void onProximityUpdate(double distance) { + try { + mCallback.get().onProximityUpdate(distance); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } } diff --git a/core/java/android/service/attention/IAttentionService.aidl b/core/java/android/service/attention/IAttentionService.aidl index 99e79973cfd2..8bb881ba1708 100644 --- a/core/java/android/service/attention/IAttentionService.aidl +++ b/core/java/android/service/attention/IAttentionService.aidl @@ -17,6 +17,7 @@ package android.service.attention; import android.service.attention.IAttentionCallback; +import android.service.attention.IProximityCallback; /** * Interface for a concrete implementation to provide to the AttentionManagerService. @@ -26,4 +27,6 @@ import android.service.attention.IAttentionCallback; oneway interface IAttentionService { void checkAttention(IAttentionCallback callback); void cancelAttentionCheck(IAttentionCallback callback); + void onStartProximityUpdates(IProximityCallback callback); + void onStopProximityUpdates(); }
\ No newline at end of file diff --git a/core/java/android/service/attention/IProximityCallback.aidl b/core/java/android/service/attention/IProximityCallback.aidl new file mode 100644 index 000000000000..9ecf9bc28e84 --- /dev/null +++ b/core/java/android/service/attention/IProximityCallback.aidl @@ -0,0 +1,10 @@ +package android.service.attention; + +/** + * Callback for onStartProximityUpdates request. + * + * @hide + */ +oneway interface IProximityCallback { + void onProximityUpdate(double distance); +} diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 29c7796d8660..cb1b5d3d20b8 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -578,6 +578,14 @@ public abstract class AutofillService extends Service { public static final String SERVICE_META_DATA = "android.autofill"; /** + * Name of the {@link FillResponse} extra used to return a delayed fill response. + * + * <p>Please see {@link FillRequest#getDelayedFillIntentSender()} on how to send a delayed + * fill response to framework.</p> + */ + public static final String EXTRA_FILL_RESPONSE = "android.service.autofill.extra.FILL_RESPONSE"; + + /** * Name of the {@link IResultReceiver} extra used to return the primary result of a request. * * @hide diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index f820f0389f0d..e4d3732361ed 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -19,6 +19,7 @@ package android.service.autofill; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -160,6 +161,19 @@ public final class FillRequest implements Parcelable { */ private final @Nullable InlineSuggestionsRequest mInlineSuggestionsRequest; + /** + * Gets the {@link IntentSender} to send a delayed fill response. + * + * <p>The autofill service must first indicate that it wants to return a delayed + * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful + * fill response. Then it can use this IntentSender to send an Intent with extra + * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p> + * + * <p>Note that this may be null if a delayed fill response is not supported for + * this fill request.</p> + */ + private final @Nullable IntentSender mDelayedFillIntentSender; + private void onConstructed() { Preconditions.checkCollectionElementsNotNull(mFillContexts, "contexts"); } @@ -252,6 +266,16 @@ public final class FillRequest implements Parcelable { * * <p>The Autofill Service must set supportsInlineSuggestions in its XML to enable support * for inline suggestions.</p> + * @param delayedFillIntentSender + * Gets the {@link IntentSender} to send a delayed fill response. + * + * <p>The autofill service must first indicate that it wants to return a delayed + * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful + * fill response. Then it can use this IntentSender to send an Intent with extra + * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p> + * + * <p>Note that this may be null if a delayed fill response is not supported for + * this fill request.</p> * @hide */ @DataClass.Generated.Member @@ -260,7 +284,8 @@ public final class FillRequest implements Parcelable { @NonNull List<FillContext> fillContexts, @Nullable Bundle clientState, @RequestFlags int flags, - @Nullable InlineSuggestionsRequest inlineSuggestionsRequest) { + @Nullable InlineSuggestionsRequest inlineSuggestionsRequest, + @Nullable IntentSender delayedFillIntentSender) { this.mId = id; this.mFillContexts = fillContexts; com.android.internal.util.AnnotationValidations.validate( @@ -276,6 +301,7 @@ public final class FillRequest implements Parcelable { | FLAG_VIEW_NOT_FOCUSED | FLAG_ACTIVITY_START); this.mInlineSuggestionsRequest = inlineSuggestionsRequest; + this.mDelayedFillIntentSender = delayedFillIntentSender; onConstructed(); } @@ -348,6 +374,22 @@ public final class FillRequest implements Parcelable { return mInlineSuggestionsRequest; } + /** + * Gets the {@link IntentSender} to send a delayed fill response. + * + * <p>The autofill service must first indicate that it wants to return a delayed + * {@link FillResponse} by setting {@link FillResponse#FLAG_DELAY_FILL} in a successful + * fill response. Then it can use this IntentSender to send an Intent with extra + * {@link AutofillService#EXTRA_FILL_RESPONSE} with the delayed response.</p> + * + * <p>Note that this may be null if a delayed fill response is not supported for + * this fill request.</p> + */ + @DataClass.Generated.Member + public @Nullable IntentSender getDelayedFillIntentSender() { + return mDelayedFillIntentSender; + } + @Override @DataClass.Generated.Member public String toString() { @@ -359,7 +401,8 @@ public final class FillRequest implements Parcelable { "fillContexts = " + mFillContexts + ", " + "clientState = " + mClientState + ", " + "flags = " + requestFlagsToString(mFlags) + ", " + - "inlineSuggestionsRequest = " + mInlineSuggestionsRequest + + "inlineSuggestionsRequest = " + mInlineSuggestionsRequest + ", " + + "delayedFillIntentSender = " + mDelayedFillIntentSender + " }"; } @@ -372,12 +415,14 @@ public final class FillRequest implements Parcelable { byte flg = 0; if (mClientState != null) flg |= 0x4; if (mInlineSuggestionsRequest != null) flg |= 0x10; + if (mDelayedFillIntentSender != null) flg |= 0x20; dest.writeByte(flg); dest.writeInt(mId); dest.writeParcelableList(mFillContexts, flags); if (mClientState != null) dest.writeBundle(mClientState); dest.writeInt(mFlags); if (mInlineSuggestionsRequest != null) dest.writeTypedObject(mInlineSuggestionsRequest, flags); + if (mDelayedFillIntentSender != null) dest.writeTypedObject(mDelayedFillIntentSender, flags); } @Override @@ -398,6 +443,7 @@ public final class FillRequest implements Parcelable { Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle(); int flags = in.readInt(); InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR); + IntentSender delayedFillIntentSender = (flg & 0x20) == 0 ? null : (IntentSender) in.readTypedObject(IntentSender.CREATOR); this.mId = id; this.mFillContexts = fillContexts; @@ -414,6 +460,7 @@ public final class FillRequest implements Parcelable { | FLAG_VIEW_NOT_FOCUSED | FLAG_ACTIVITY_START); this.mInlineSuggestionsRequest = inlineSuggestionsRequest; + this.mDelayedFillIntentSender = delayedFillIntentSender; onConstructed(); } @@ -433,10 +480,10 @@ public final class FillRequest implements Parcelable { }; @DataClass.Generated( - time = 1643052544776L, + time = 1643386870464L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java", - inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_ACTIVITY_START\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") + inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_ACTIVITY_START\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 903e77fcb3d7..296877a448ab 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -65,10 +65,22 @@ public final class FillResponse implements Parcelable { */ public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2; + /** + * Flag used to request to wait for a delayed fill from the remote Autofill service if it's + * passed to {@link Builder#setFlags(int)}. + * + * <p>Some datasets (i.e. OTP) take time to produce. This flags allows remote service to send + * a {@link FillResponse} to the latest {@link FillRequest} via + * {@link FillRequest#getDelayedFillIntentSender()} even if the original {@link FillCallback} + * has timed out. + */ + public static final int FLAG_DELAY_FILL = 0x4; + /** @hide */ @IntDef(flag = true, prefix = { "FLAG_" }, value = { FLAG_TRACK_CONTEXT_COMMITED, - FLAG_DISABLE_ACTIVITY_ONLY + FLAG_DISABLE_ACTIVITY_ONLY, + FLAG_DELAY_FILL }) @Retention(RetentionPolicy.SOURCE) @interface FillResponseFlags {} @@ -657,7 +669,7 @@ public final class FillResponse implements Parcelable { public Builder setFlags(@FillResponseFlags int flags) { throwIfDestroyed(); mFlags = Preconditions.checkFlagsArgument(flags, - FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY); + FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY | FLAG_DELAY_FILL); return this; } diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java index 870a7e3f2646..9df83587f125 100644 --- a/core/java/android/service/games/GameService.java +++ b/core/java/android/service/games/GameService.java @@ -16,9 +16,11 @@ package android.service.games; +import android.Manifest; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.app.IGameManagerService; @@ -173,6 +175,7 @@ public class GameService extends Service { * * @param taskId The taskId of the game. */ + @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY) public final void createGameSession(@IntRange(from = 0) int taskId) { if (mGameServiceController == null) { throw new IllegalStateException("Can not call before connected()"); diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java index f4baedc18acf..e33f1801129b 100644 --- a/core/java/android/service/games/GameSession.java +++ b/core/java/android/service/games/GameSession.java @@ -84,6 +84,15 @@ public abstract class GameSession { } @Override + public void onTransientSystemBarVisibilityFromRevealGestureChanged( + boolean visibleDueToGesture) { + Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( + GameSession::dispatchTransientSystemBarVisibilityFromRevealGestureChanged, + GameSession.this, + visibleDueToGesture)); + } + + @Override public void onTaskFocusChanged(boolean focused) { Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( GameSession::moveToState, GameSession.this, @@ -109,6 +118,7 @@ public abstract class GameSession { } private LifecycleState mLifecycleState = LifecycleState.INITIALIZED; + private boolean mAreTransientInsetsVisibleDueToGesture = false; private IGameSessionController mGameSessionController; private int mTaskId; private GameSessionRootView mGameSessionRootView; @@ -138,11 +148,23 @@ public abstract class GameSession { } @Hide - void doDestroy() { + private void doDestroy() { mSurfaceControlViewHost.release(); moveToState(LifecycleState.DESTROYED); } + /** @hide */ + @VisibleForTesting + @MainThread + public void dispatchTransientSystemBarVisibilityFromRevealGestureChanged( + boolean visibleDueToGesture) { + boolean didValueChange = mAreTransientInsetsVisibleDueToGesture != visibleDueToGesture; + mAreTransientInsetsVisibleDueToGesture = visibleDueToGesture; + if (didValueChange) { + onTransientSystemBarVisibilityFromRevealGestureChanged(visibleDueToGesture); + } + } + /** * @hide */ @@ -252,7 +274,23 @@ public abstract class GameSession { * * @param focused True if the game task is focused, false if the game task is unfocused. */ - public void onGameTaskFocusChanged(boolean focused) {} + public void onGameTaskFocusChanged(boolean focused) { + } + + /** + * Called when the visibility of the transient system bars changed due to the user performing + * the reveal gesture. The reveal gesture is defined as a swipe to reveal the transient system + * bars that originates from the system bars. + * + * @param visibleDueToGesture if the transient bars triggered by the reveal gesture are visible. + * This is {@code true} when the transient system bars become visible + * due to user performing the reveal gesture. This is {@code false} + * when the transient system bars are hidden or become permanently + * visible. + */ + public void onTransientSystemBarVisibilityFromRevealGestureChanged( + boolean visibleDueToGesture) { + } /** * Sets the task overlay content to an explicit view. This view is placed directly into the game @@ -278,7 +316,7 @@ public abstract class GameSession { * * @return {@code true} if the game was successfully restarted; otherwise, {@code false}. */ - @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) + @RequiresPermission(android.Manifest.permission.MANAGE_GAME_ACTIVITY) public final boolean restartGame() { try { mGameSessionController.restartGame(mTaskId); @@ -344,12 +382,14 @@ public abstract class GameSession { /** * Called when taking the screenshot failed. + * * @param statusCode Indicates the reason for failure. */ void onFailure(@ScreenshotFailureStatus int statusCode); /** * Called when taking the screenshot succeeded. + * * @param bitmap The screenshot. */ void onSuccess(@NonNull Bitmap bitmap); diff --git a/core/java/android/service/games/IGameSession.aidl b/core/java/android/service/games/IGameSession.aidl index 71da6302b63d..49c36c6a301c 100644 --- a/core/java/android/service/games/IGameSession.aidl +++ b/core/java/android/service/games/IGameSession.aidl @@ -21,5 +21,6 @@ package android.service.games; */ oneway interface IGameSession { void onDestroyed(); + void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean visibleDueToGesture); void onTaskFocusChanged(boolean focused); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index f2a03558663e..c91851a8896d 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -905,11 +905,12 @@ public abstract class WallpaperService extends Service { if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) { return; } + + SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction(); // TODO: apply the dimming to preview as well once surface transparency works in // preview mode. if (!isPreview() && mShouldDim) { Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount); - SurfaceControl.Transaction surfaceControl = new SurfaceControl.Transaction(); // Animate dimming to gradually change the wallpaper alpha from the previous // dim amount to the new amount only if the dim amount changed. @@ -919,16 +920,15 @@ public abstract class WallpaperService extends Service { ? 0 : DIMMING_ANIMATION_DURATION_MS); animator.addUpdateListener((ValueAnimator va) -> { final float dimValue = (float) va.getAnimatedValue(); - surfaceControl - .setAlpha(mBbqSurfaceControl, 1 - dimValue) - .apply(); + if (mBbqSurfaceControl != null) { + surfaceControlTransaction + .setAlpha(mBbqSurfaceControl, 1 - dimValue).apply(); + } }); animator.start(); } else { Log.v(TAG, "Setting wallpaper dimming: " + 0); - new SurfaceControl.Transaction() - .setAlpha(mBbqSurfaceControl, 1.0f) - .apply(); + surfaceControlTransaction.setAlpha(mBbqSurfaceControl, 1.0f).apply(); } } diff --git a/core/java/android/util/IconDrawableFactory.java b/core/java/android/util/IconDrawableFactory.java index b5e8dd7ed0af..5bb263a1f463 100644 --- a/core/java/android/util/IconDrawableFactory.java +++ b/core/java/android/util/IconDrawableFactory.java @@ -15,7 +15,12 @@ */ package android.util; +import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; +import static android.app.admin.DevicePolicyResources.Drawables.UNDEFINED; +import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON_BADGE; + import android.annotation.UserIdInt; +import android.app.admin.DevicePolicyManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -37,6 +42,7 @@ public class IconDrawableFactory { protected final Context mContext; protected final PackageManager mPm; protected final UserManager mUm; + protected final DevicePolicyManager mDpm; protected final LauncherIcons mLauncherIcons; protected final boolean mEmbedShadow; @@ -44,6 +50,7 @@ public class IconDrawableFactory { mContext = context; mPm = context.getPackageManager(); mUm = context.getSystemService(UserManager.class); + mDpm = context.getSystemService(DevicePolicyManager.class); mLauncherIcons = new LauncherIcons(context); mEmbedShadow = embedShadow; } @@ -73,18 +80,32 @@ public class IconDrawableFactory { if (appInfo.isInstantApp()) { int badgeColor = Resources.getSystem().getColor( com.android.internal.R.color.instant_app_badge, null); + Drawable badge = mContext.getDrawable( + com.android.internal.R.drawable.ic_instant_icon_badge_bolt); icon = mLauncherIcons.getBadgedDrawable(icon, - com.android.internal.R.drawable.ic_instant_icon_badge_bolt, + badge, badgeColor); } if (mUm.hasBadge(userId)) { - icon = mLauncherIcons.getBadgedDrawable(icon, - mUm.getUserIconBadgeResId(userId), - mUm.getUserBadgeColor(userId)); + + Drawable badge = mDpm.getDrawable( + getUpdatableUserIconBadgeId(userId), + SOLID_COLORED, + () -> getDefaultUserIconBadge(userId)); + + icon = mLauncherIcons.getBadgedDrawable(icon, badge, mUm.getUserBadgeColor(userId)); } return icon; } + private String getUpdatableUserIconBadgeId(int userId) { + return mUm.isManagedProfile(userId) ? WORK_PROFILE_ICON_BADGE : UNDEFINED; + } + + private Drawable getDefaultUserIconBadge(int userId) { + return mContext.getResources().getDrawable(mUm.getUserIconBadgeResId(userId)); + } + /** * Add shadow to the icon if {@link AdaptiveIconDrawable} */ diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java index e652e17bceb3..355b2e9934fd 100644 --- a/core/java/android/util/LauncherIcons.java +++ b/core/java/android/util/LauncherIcons.java @@ -45,10 +45,12 @@ public final class LauncherIcons { private final SparseArray<Bitmap> mShadowCache = new SparseArray<>(); private final int mIconSize; private final Resources mRes; + private final Context mContext; public LauncherIcons(Context context) { mRes = context.getResources(); mIconSize = mRes.getDimensionPixelSize(android.R.dimen.app_icon_size); + mContext = context; } public Drawable wrapIconDrawableWithShadow(Drawable drawable) { @@ -98,14 +100,14 @@ public final class LauncherIcons { return shadow; } - public Drawable getBadgeDrawable(int foregroundRes, int backgroundColor) { - return getBadgedDrawable(null, foregroundRes, backgroundColor); + public Drawable getBadgeDrawable(Drawable badgeForeground, int backgroundColor) { + return getBadgedDrawable(null, badgeForeground, backgroundColor); } - public Drawable getBadgedDrawable(Drawable base, int foregroundRes, int backgroundColor) { + public Drawable getBadgedDrawable( + Drawable base, Drawable badgeForeground, int backgroundColor) { Resources overlayableRes = ActivityThread.currentActivityThread().getApplication().getResources(); - // ic_corp_icon_badge_shadow is not work-profile-specific. Drawable badgeShadow = overlayableRes.getDrawable( com.android.internal.R.drawable.ic_corp_icon_badge_shadow); @@ -115,7 +117,6 @@ public final class LauncherIcons { com.android.internal.R.drawable.ic_corp_icon_badge_color) .getConstantState().newDrawable().mutate(); - Drawable badgeForeground = overlayableRes.getDrawable(foregroundRes); badgeForeground.setTint(backgroundColor); Drawable[] drawables = base == null diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl index 449e9b325904..67ae7430e0b7 100644 --- a/core/java/android/view/IDisplayWindowListener.aidl +++ b/core/java/android/view/IDisplayWindowListener.aidl @@ -63,5 +63,5 @@ oneway interface IDisplayWindowListener { /** * Called when the keep clear ares on a display have changed. */ - void onKeepClearAreasChanged(int displayId, in List<Rect> keepClearAreas); + void onKeepClearAreasChanged(int displayId, in List<Rect> restricted, in List<Rect> unrestricted); } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 039b50a637be..8401b7cdb243 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -295,6 +295,9 @@ public abstract class Window { private OnWindowDismissedCallback mOnWindowDismissedCallback; private OnWindowSwipeDismissedCallback mOnWindowSwipeDismissedCallback; private WindowControllerCallback mWindowControllerCallback; + @WindowInsetsController.Appearance + private int mSystemBarAppearance; + private DecorCallback mDecorCallback; private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener; private Rect mRestrictedCaptionAreaRect; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) @@ -607,17 +610,6 @@ public abstract class Window { * @param hasCapture True if the window has pointer capture. */ default public void onPointerCaptureChanged(boolean hasCapture) { }; - - /** - * Called from - * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}. - * - * @param appearance The newly applied appearance. - * @hide - */ - default void onSystemBarAppearanceChanged( - @WindowInsetsController.Appearance int appearance) { - } } /** @hide */ @@ -672,6 +664,35 @@ public abstract class Window { void updateNavigationBarColor(int color); } + /** @hide */ + public interface DecorCallback { + /** + * Called from + * {@link com.android.internal.policy.DecorView#onSystemBarAppearanceChanged(int)}. + * + * @param appearance The newly applied appearance. + */ + void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance); + + /** + * Called from + * {@link com.android.internal.policy.DecorView#updateColorViews(WindowInsets, boolean)} + * when {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackground} is + * being updated. + * + * @param drawLegacyNavigationBarBackground the new value that is being set to + * {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackground}. + * @return The value to be set to + * {@link com.android.internal.policy.DecorView#mDrawLegacyNavigationBarBackgroundHandled} + * on behalf of the {@link com.android.internal.policy.DecorView}. + * {@code true} to tell that the Window can render the legacy navigation bar + * background on behalf of the {@link com.android.internal.policy.DecorView}. + * {@code false} to let {@link com.android.internal.policy.DecorView} handle it. + */ + boolean onDrawLegacyNavigationBarBackgroundChanged( + boolean drawLegacyNavigationBarBackground); + } + /** * Callback for clients that want to be aware of where caption draws content. */ @@ -996,6 +1017,36 @@ public abstract class Window { return mWindowControllerCallback; } + /** @hide */ + public final void setDecorCallback(DecorCallback decorCallback) { + mDecorCallback = decorCallback; + } + + /** @hide */ + @WindowInsetsController.Appearance + public final int getSystemBarAppearance() { + return mSystemBarAppearance; + } + + /** @hide */ + public final void dispatchOnSystemBarAppearanceChanged( + @WindowInsetsController.Appearance int appearance) { + mSystemBarAppearance = appearance; + if (mDecorCallback != null) { + mDecorCallback.onSystemBarAppearanceChanged(appearance); + } + } + + /** @hide */ + public final boolean onDrawLegacyNavigationBarBackgroundChanged( + boolean drawLegacyNavigationBarBackground) { + if (mDecorCallback == null) { + return false; + } + return mDecorCallback.onDrawLegacyNavigationBarBackgroundChanged( + drawLegacyNavigationBarBackground); + } + /** * Set a callback for changes of area where caption will draw its content. * diff --git a/core/java/android/view/WindowCallbackWrapper.java b/core/java/android/view/WindowCallbackWrapper.java index 115e9e891408..02c8945d9fce 100644 --- a/core/java/android/view/WindowCallbackWrapper.java +++ b/core/java/android/view/WindowCallbackWrapper.java @@ -163,10 +163,5 @@ public class WindowCallbackWrapper implements Window.Callback { public void onPointerCaptureChanged(boolean hasCapture) { mWrapped.onPointerCaptureChanged(hasCapture); } - - @Override - public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) { - mWrapped.onSystemBarAppearanceChanged(appearance); - } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 0a33d6cd82ea..a31cacfdfd2b 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -710,6 +710,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY = 0x0400000; + private static final int BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE = 0x0800000; + /** * Bits that provide the id of a virtual descendant of a view. */ @@ -2276,6 +2278,38 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets if the node has selectable text. + * + * <p> + * Services should use {@link #ACTION_SET_SELECTION} for selection. Editable text nodes must + * also be selectable. But not all UIs will populate this field, so services should consider + * 'isTextSelectable | isEditable' to ensure they don't miss nodes with selectable text. + * </p> + * + * @see #isEditable + * @return True if the node has selectable text. + */ + public boolean isTextSelectable() { + return getBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE); + } + + /** + * Sets if the node has selectable text. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param selectableText True if the node has selectable text, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setTextSelectable(boolean selectableText) { + setBooleanProperty(BOOLEAN_PROPERTY_IS_TEXT_SELECTABLE, selectableText); + } + + /** * Gets if the node is editable. * * @return True if the node is editable, false otherwise. @@ -4327,8 +4361,12 @@ public class AccessibilityNodeInfo implements Parcelable { return "ACTION_CANCEL_DRAG"; case R.id.accessibilityActionDragDrop: return "ACTION_DROP"; - default: + default: { + if (action == R.id.accessibilityActionShowSuggestions) { + return "ACTION_SHOW_SUGGESTIONS"; + } return "ACTION_UNKNOWN"; + } } } @@ -4462,6 +4500,7 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; importantForAccessibility: ").append(isImportantForAccessibility()); builder.append("; visible: ").append(isVisibleToUser()); builder.append("; actions: ").append(mActions); + builder.append("; isTextSelectable: ").append(isTextSelectable()); return builder.toString(); } diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index ab749ee284a8..3914a3c963b6 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -865,6 +865,15 @@ public abstract class Animation implements Cloneable { } /** + * @return if a window animation has outsets applied to it. + * + * @hide + */ + public boolean hasExtension() { + return false; + } + + /** * If showBackground is {@code true} and this animation is applied on a window, then the windows * in the animation will animate with the background associated with this window behind them. * @@ -942,6 +951,21 @@ public abstract class Animation implements Cloneable { } /** + * Gets the transformation to apply a specific point in time. Implementations of this method + * should always be kept in sync with getTransformation. + * + * @param normalizedTime time between 0 and 1 where 0 is the start of the animation and 1 the + * end. + * @param outTransformation A transformation object that is provided by the + * caller and will be filled in by the animation. + * @hide + */ + public void getTransformationAt(float normalizedTime, Transformation outTransformation) { + final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); + applyTransformation(interpolatedTime, outTransformation); + } + + /** * Gets the transformation to apply at a specified point in time. Implementations of this * method should always replace the specified Transformation or document they are doing * otherwise. @@ -987,8 +1011,7 @@ public abstract class Animation implements Cloneable { normalizedTime = 1.0f - normalizedTime; } - final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); - applyTransformation(interpolatedTime, outTransformation); + getTransformationAt(normalizedTime, outTransformation); } if (expired) { diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java index 03c6ca69a592..a2f3544c70ab 100644 --- a/core/java/android/view/animation/AnimationSet.java +++ b/core/java/android/view/animation/AnimationSet.java @@ -363,6 +363,26 @@ public class AnimationSet extends Animation { * The transformation of an animation set is the concatenation of all of its * component animations. * + * @see android.view.animation.Animation#getTransformationAt + * @hide + */ + @Override + public void getTransformationAt(float interpolatedTime, Transformation t) { + final Transformation temp = mTempTransformation; + + for (int i = mAnimations.size() - 1; i >= 0; --i) { + final Animation a = mAnimations.get(i); + + temp.clear(); + a.getTransformationAt(interpolatedTime, t); + t.compose(temp); + } + } + + /** + * The transformation of an animation set is the concatenation of all of its + * component animations. + * * @see android.view.animation.Animation#getTransformation */ @Override @@ -517,4 +537,15 @@ public class AnimationSet extends Animation { public boolean willChangeBounds() { return (mFlags & PROPERTY_CHANGE_BOUNDS_MASK) == PROPERTY_CHANGE_BOUNDS_MASK; } + + /** @hide */ + @Override + public boolean hasExtension() { + for (Animation animation : mAnimations) { + if (animation.hasExtension()) { + return true; + } + } + return false; + } } diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index 7ce0f4571f76..7d1dc7660871 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -190,6 +190,8 @@ public class AnimationUtils { anim = new TranslateAnimation(c, attrs); } else if (name.equals("cliprect")) { anim = new ClipRectAnimation(c, attrs); + } else if (name.equals("extend")) { + anim = new ExtendAnimation(c, attrs); } else { throw new RuntimeException("Unknown animation name: " + parser.getName()); } diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java new file mode 100644 index 000000000000..fd627e50ab0e --- /dev/null +++ b/core/java/android/view/animation/ExtendAnimation.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.animation; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Insets; +import android.util.AttributeSet; + +/** + * An animation that controls the outset of an object. + * + * @hide + */ +public class ExtendAnimation extends Animation { + protected Insets mFromInsets = Insets.NONE; + protected Insets mToInsets = Insets.NONE; + + private int mFromLeftType = ABSOLUTE; + private int mFromTopType = ABSOLUTE; + private int mFromRightType = ABSOLUTE; + private int mFromBottomType = ABSOLUTE; + + private int mToLeftType = ABSOLUTE; + private int mToTopType = ABSOLUTE; + private int mToRightType = ABSOLUTE; + private int mToBottomType = ABSOLUTE; + + private float mFromLeftValue; + private float mFromTopValue; + private float mFromRightValue; + private float mFromBottomValue; + + private float mToLeftValue; + private float mToTopValue; + private float mToRightValue; + private float mToBottomValue; + + /** + * Constructor used when an ExtendAnimation is loaded from a resource. + * + * @param context Application context to use + * @param attrs Attribute set from which to read values + */ + public ExtendAnimation(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ExtendAnimation); + + Description d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_fromExtendLeft)); + mFromLeftType = d.type; + mFromLeftValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_fromExtendTop)); + mFromTopType = d.type; + mFromTopValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_fromExtendRight)); + mFromRightType = d.type; + mFromRightValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_fromExtendBottom)); + mFromBottomType = d.type; + mFromBottomValue = d.value; + + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_toExtendLeft)); + mToLeftType = d.type; + mToLeftValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_toExtendTop)); + mToTopType = d.type; + mToTopValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_toExtendRight)); + mToRightType = d.type; + mToRightValue = d.value; + + d = Description.parseValue(a.peekValue( + com.android.internal.R.styleable.ExtendAnimation_toExtendBottom)); + mToBottomType = d.type; + mToBottomValue = d.value; + + a.recycle(); + } + + /** + * Constructor to use when building an ExtendAnimation from code + * + * @param fromInsets the insets to animate from + * @param toInsets the insets to animate to + */ + public ExtendAnimation(Insets fromInsets, Insets toInsets) { + if (fromInsets == null || toInsets == null) { + throw new RuntimeException("Expected non-null animation outsets"); + } + mFromLeftValue = -fromInsets.left; + mFromTopValue = -fromInsets.top; + mFromRightValue = -fromInsets.right; + mFromBottomValue = -fromInsets.bottom; + + mToLeftValue = -toInsets.left; + mToTopValue = -toInsets.top; + mToRightValue = -toInsets.right; + mToBottomValue = -toInsets.bottom; + } + + /** + * Constructor to use when building an ExtendAnimation from code + */ + public ExtendAnimation(int fromL, int fromT, int fromR, int fromB, + int toL, int toT, int toR, int toB) { + this(Insets.of(-fromL, -fromT, -fromR, -fromB), Insets.of(-toL, -toT, -toR, -toB)); + } + + @Override + protected void applyTransformation(float it, Transformation tr) { + int l = mFromInsets.left + (int) ((mToInsets.left - mFromInsets.left) * it); + int t = mFromInsets.top + (int) ((mToInsets.top - mFromInsets.top) * it); + int r = mFromInsets.right + (int) ((mToInsets.right - mFromInsets.right) * it); + int b = mFromInsets.bottom + (int) ((mToInsets.bottom - mFromInsets.bottom) * it); + tr.setInsets(l, t, r, b); + } + + @Override + public boolean willChangeTransformationMatrix() { + return false; + } + + /** @hide */ + @Override + public boolean hasExtension() { + return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0 + || mFromInsets.bottom < 0; + } + + @Override + public void initialize(int width, int height, int parentWidth, int parentHeight) { + super.initialize(width, height, parentWidth, parentHeight); + // We remove any negative extension (i.e. positive insets) and set those to 0 + mFromInsets = Insets.min(Insets.of( + -(int) resolveSize(mFromLeftType, mFromLeftValue, width, parentWidth), + -(int) resolveSize(mFromTopType, mFromTopValue, height, parentHeight), + -(int) resolveSize(mFromRightType, mFromRightValue, width, parentWidth), + -(int) resolveSize(mFromBottomType, mFromBottomValue, height, parentHeight) + ), Insets.NONE); + mToInsets = Insets.min(Insets.of( + -(int) resolveSize(mToLeftType, mToLeftValue, width, parentWidth), + -(int) resolveSize(mToTopType, mToTopValue, height, parentHeight), + -(int) resolveSize(mToRightType, mToRightValue, width, parentWidth), + -(int) resolveSize(mToBottomType, mToBottomValue, height, parentHeight) + ), Insets.NONE); + } +} diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java index b35a66e6eb26..bd623088017a 100644 --- a/core/java/android/view/animation/Transformation.java +++ b/core/java/android/view/animation/Transformation.java @@ -18,6 +18,7 @@ package android.view.animation; import android.annotation.FloatRange; import android.compat.annotation.UnsupportedAppUsage; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Rect; @@ -53,6 +54,8 @@ public class Transformation { private boolean mHasClipRect; private Rect mClipRect = new Rect(); + private Insets mInsets = Insets.NONE; + /** * Creates a new transformation with alpha = 1 and the identity matrix. */ @@ -132,8 +135,9 @@ public class Transformation { setClipRect(bounds); } } + setInsets(Insets.add(getInsets(), t.getInsets())); } - + /** * Like {@link #compose(Transformation)} but does this.postConcat(t) of * the transformation matrix. @@ -160,7 +164,7 @@ public class Transformation { public Matrix getMatrix() { return mMatrix; } - + /** * Sets the degree of transparency * @param alpha 1.0 means fully opaqe and 0.0 means fully transparent @@ -170,6 +174,13 @@ public class Transformation { } /** + * @return The degree of transparency + */ + public float getAlpha() { + return mAlpha; + } + + /** * Sets the current Transform's clip rect * @hide */ @@ -203,12 +214,29 @@ public class Transformation { } /** - * @return The degree of transparency + * Sets the current Transform's insets + * @hide */ - public float getAlpha() { - return mAlpha; + public void setInsets(Insets insets) { + mInsets = insets; } - + + /** + * Sets the current Transform's insets + * @hide + */ + public void setInsets(int left, int top, int right, int bottom) { + mInsets = Insets.of(left, top, right, bottom); + } + + /** + * Returns the current Transform's outset rect + * @hide + */ + public Insets getInsets() { + return mInsets; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(64); @@ -216,7 +244,7 @@ public class Transformation { toShortString(sb); return sb.toString(); } - + /** * Return a string representation of the transformation in a compact form. */ @@ -225,7 +253,7 @@ public class Transformation { toShortString(sb); return sb.toString(); } - + /** * @hide */ @@ -234,7 +262,7 @@ public class Transformation { sb.append(" matrix="); sb.append(mMatrix.toShortString()); sb.append('}'); } - + /** * Print short string, to optimize dumping. * @hide diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 955269160e49..2134d819943e 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -291,6 +291,8 @@ public abstract class ContentCaptureSession implements AutoCloseable { * <p>Typically used to change the context associated with the default session from an activity. */ public final void setContentCaptureContext(@Nullable ContentCaptureContext context) { + if (!isContentCaptureEnabled()) return; + mClientContext = context; updateContentCaptureContext(context); } diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index ff6903e814b7..08cc31c8534f 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -105,12 +105,14 @@ public interface InputMethod { * current IME. * @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME. * @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME. + * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be + * shown while the IME is shown. * @hide */ @MainThread default void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations, int configChanges, - boolean stylusHwSupported) { + boolean stylusHwSupported, boolean shouldShowImeSwitcherWhenImeIsShown) { attachToken(token); } @@ -229,6 +231,8 @@ public interface InputMethod { * the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as * long as your implementation of {@link InputMethod} relies on such * IPCs + * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be + * shown while the IME is shown. * @see #startInput(InputConnection, EditorInfo) * @see #restartInput(InputConnection, EditorInfo) * @see EditorInfo @@ -237,7 +241,7 @@ public interface InputMethod { @MainThread default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken) { + @NonNull IBinder startInputToken, boolean shouldShowImeSwitcherWhenImeIsShown) { if (restarting) { restartInput(inputConnection, editorInfo); } else { @@ -246,6 +250,18 @@ public interface InputMethod { } /** + * Notifies that whether the IME should show the IME switcher or not is being changed. + * + * @param shouldShowImeSwitcherWhenImeIsShown {@code true} If the IME switcher is expected to be + * shown while the IME is shown. + * @hide + */ + @MainThread + default void onShouldShowImeSwitcherWhenImeIsShownChanged( + boolean shouldShowImeSwitcherWhenImeIsShown) { + } + + /** * Create a new {@link InputMethodSession} that can be handed to client * applications for interacting with the input method. You can later * use {@link #revokeSession(InputMethodSession)} to destroy the session diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index c6f64f4ad633..b00a3829f468 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -301,6 +301,13 @@ public class RemoteViews implements Parcelable, Filter { public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4; /** + * This mask determines which flags are propagated to nested RemoteViews (either added by + * addView, or set as portrait/landscape/sized RemoteViews). + */ + static final int FLAG_MASK_TO_PROPAGATE = + FLAG_WIDGET_IS_COLLECTION_CHILD | FLAG_USE_LIGHT_BACKGROUND_LAYOUT; + + /** * A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is * intentionally a different instance in order to trick Bundle reader so that it doesn't allow * lazy initialization. @@ -467,6 +474,18 @@ public class RemoteViews implements Parcelable, Filter { */ public void addFlags(@ApplyFlags int flags) { mApplyFlags = mApplyFlags | flags; + + int flagsToPropagate = flags & FLAG_MASK_TO_PROPAGATE; + if (flagsToPropagate != 0) { + if (hasSizedRemoteViews()) { + for (RemoteViews remoteView : mSizedRemoteViews) { + remoteView.addFlags(flagsToPropagate); + } + } else if (hasLandscapeAndPortraitLayouts()) { + mLandscape.addFlags(flagsToPropagate); + mPortrait.addFlags(flagsToPropagate); + } + } } /** @@ -2407,6 +2426,10 @@ public class RemoteViews implements Parcelable, Filter { // will return -1. final int nextChild = getNextRecyclableChild(target); RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context); + + int flagsToPropagate = mApplyFlags & FLAG_MASK_TO_PROPAGATE; + if (flagsToPropagate != 0) rvToApply.addFlags(flagsToPropagate); + if (nextChild >= 0 && mStableId != NO_ID) { // At that point, the views starting at index nextChild are the ones recyclable but // not yet recycled. All views added on that round of application are placed before. @@ -2419,8 +2442,8 @@ public class RemoteViews implements Parcelable, Filter { target.removeViews(nextChild, recycledViewIndex - nextChild); } setNextRecyclableChild(target, nextChild + 1, target.getChildCount()); - rvToApply.reapply(context, child, handler, null /* size */, colorResources, - false /* topLevel */); + rvToApply.reapplyNestedViews(context, child, rootParent, handler, + null /* size */, colorResources); return; } // If we cannot recycle the views, we still remove all views in between to @@ -2431,8 +2454,8 @@ public class RemoteViews implements Parcelable, Filter { // If we cannot recycle, insert the new view before the next recyclable child. // Inflate nested views and add as children - View nestedView = rvToApply.apply(context, target, handler, null /* size */, - colorResources); + View nestedView = rvToApply.applyNestedViews(context, target, rootParent, handler, + null /* size */, colorResources); if (mStableId != NO_ID) { setStableId(nestedView, mStableId); } @@ -3780,7 +3803,7 @@ public class RemoteViews implements Parcelable, Filter { * @param parcel */ public RemoteViews(Parcel parcel) { - this(parcel, /* rootParent= */ null, /* info= */ null, /* depth= */ 0); + this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0); } private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData, @@ -5580,6 +5603,16 @@ public class RemoteViews implements Parcelable, Filter { return result; } + private View applyNestedViews(Context context, ViewGroup directParent, + ViewGroup rootParent, InteractionHandler handler, SizeF size, + ColorResources colorResources) { + RemoteViews rvToApply = getRemoteViewsToApply(context, size); + + View result = inflateView(context, rvToApply, directParent, 0, colorResources); + rvToApply.performApply(result, rootParent, handler, colorResources); + return result; + } + private View inflateView(Context context, RemoteViews rv, ViewGroup parent) { return inflateView(context, rv, parent, 0, null); } @@ -5895,6 +5928,12 @@ public class RemoteViews implements Parcelable, Filter { } } + private void reapplyNestedViews(Context context, View v, ViewGroup rootParent, + InteractionHandler handler, SizeF size, ColorResources colorResources) { + RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); + rvToApply.performApply(v, rootParent, handler, colorResources); + } + /** * Applies all the actions to the provided view, moving as much of the task on the background * thread as possible. diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 41c540116928..0fe2ed51beb6 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4461,7 +4461,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * pixel" units. This size is adjusted based on the current density and * user font size preference. * - * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. + * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op. * * @param size The scaled pixel size. * @@ -4476,7 +4476,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Set the default text size to a given unit and value. See {@link * TypedValue} for the possible dimension units. * - * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. + * <p>Note: if this TextView has the auto-size feature enabled, then this function is no-op. * * @param unit The desired dimension unit. * @param size The desired size in the given units. @@ -12289,6 +12289,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener EXTRA_DATA_RENDERING_INFO_KEY, EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY )); + info.setTextSelectable(isTextSelectable()); } else { info.setAvailableExtraData(Arrays.asList( EXTRA_DATA_RENDERING_INFO_KEY diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 9648008274ef..629a1b36b9e6 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -113,7 +113,7 @@ interface IBatteryStats { void notePhoneOn(); void notePhoneOff(); void notePhoneSignalStrength(in SignalStrength signalStrength); - void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType); + void notePhoneDataConnectionState(int dataType, boolean hasData, int serviceType, int nrFrequency); void notePhoneState(int phoneState); void noteWifiOn(); void noteWifiOff(); @@ -145,6 +145,8 @@ interface IBatteryStats { long getAwakeTimeBattery(); long getAwakeTimePlugged(); + void noteBluetoothOn(int uid, int reason, String packageName); + void noteBluetoothOff(int uid, int reason, String packageName); void noteBleScanStarted(in WorkSource ws, boolean isUnoptimized); void noteBleScanStopped(in WorkSource ws, boolean isUnoptimized); void noteBleScanReset(); diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/InstallLocationUtils.java index c2f20526f8fd..c456cf3a4bb6 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/InstallLocationUtils.java @@ -16,6 +16,7 @@ package com.android.internal.content; +import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL; import android.content.Context; @@ -53,7 +54,7 @@ import java.util.UUID; * and media container service transports. * Some utility methods to invoke StorageManagerService api. */ -public class PackageHelper { +public class InstallLocationUtils { public static final int RECOMMEND_INSTALL_INTERNAL = 1; public static final int RECOMMEND_INSTALL_EXTERNAL = 2; public static final int RECOMMEND_INSTALL_EPHEMERAL = 3; @@ -89,9 +90,13 @@ public class PackageHelper { */ public static abstract class TestableInterface { abstract public StorageManager getStorageManager(Context context); + abstract public boolean getForceAllowOnExternalSetting(Context context); + abstract public boolean getAllow3rdPartyOnInternalConfig(Context context); + abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName); + abstract public File getDataDirectory(); } @@ -150,11 +155,11 @@ public class PackageHelper { /** * Given a requested {@link PackageInfo#installLocation} and calculated * install size, pick the actual volume to install the app. Only considers - * internal and private volumes, and prefers to keep an existing package on + * internal and private volumes, and prefers to keep an existing package onocation * its current volume. * * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null} - * for internal storage. + * for internal storage. */ public static String resolveInstallVolume(Context context, SessionParams params) throws IOException { @@ -316,21 +321,6 @@ public class PackageHelper { && params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile()); } - @Deprecated - public static int resolveInstallLocation(Context context, String packageName, - int installLocation, long sizeBytes, int installFlags) { - final SessionParams params = new SessionParams(SessionParams.MODE_INVALID); - params.appPackageName = packageName; - params.installLocation = installLocation; - params.sizeBytes = sizeBytes; - params.installFlags = installFlags; - try { - return resolveInstallLocation(context, params); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - /** * Given a requested {@link PackageInfo#installLocation} and calculated * install size, pick the actual location to install the app. @@ -393,24 +383,24 @@ public class PackageHelper { // and will fall through to return INSUFFICIENT_STORAGE if (fitsOnInternal) { return (ephemeral) - ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL - : PackageHelper.RECOMMEND_INSTALL_INTERNAL; + ? InstallLocationUtils.RECOMMEND_INSTALL_EPHEMERAL + : InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL; } } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) { if (fitsOnExternal) { - return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; + return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL; } } if (checkBoth) { if (fitsOnInternal) { - return PackageHelper.RECOMMEND_INSTALL_INTERNAL; + return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL; } else if (fitsOnExternal) { - return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; + return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL; } } - return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; + return InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; } @Deprecated @@ -476,4 +466,48 @@ public class PackageHelper { return 0; } } + + public static int installLocationPolicy(int installLocation, int recommendedInstallLocation, + int installFlags, boolean installedPkgIsSystem, boolean installedPackageOnExternal) { + if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) { + // Invalid install. Return error code + return RECOMMEND_FAILED_ALREADY_EXISTS; + } + // Check for updated system application. + if (installedPkgIsSystem) { + return RECOMMEND_INSTALL_INTERNAL; + } + // If current upgrade specifies particular preference + if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { + // Application explicitly specified internal. + return RECOMMEND_INSTALL_INTERNAL; + } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { + // App explicitly prefers external. Let policy decide + return recommendedInstallLocation; + } else { + // Prefer previous location + if (installedPackageOnExternal) { + return RECOMMEND_INSTALL_EXTERNAL; + } + return RECOMMEND_INSTALL_INTERNAL; + } + } + + public static int getInstallationErrorCode(int loc) { + if (loc == RECOMMEND_FAILED_INVALID_LOCATION) { + return PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; + } else if (loc == RECOMMEND_FAILED_ALREADY_EXISTS) { + return PackageManager.INSTALL_FAILED_ALREADY_EXISTS; + } else if (loc == RECOMMEND_FAILED_INSUFFICIENT_STORAGE) { + return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } else if (loc == RECOMMEND_FAILED_INVALID_APK) { + return PackageManager.INSTALL_FAILED_INVALID_APK; + } else if (loc == RECOMMEND_FAILED_INVALID_URI) { + return PackageManager.INSTALL_FAILED_INVALID_URI; + } else if (loc == RECOMMEND_MEDIA_UNAVAILABLE) { + return PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE; + } else { + return INSTALL_SUCCEEDED; + } + } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 25ee2d0fa019..c0fec62bdd94 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -69,10 +69,14 @@ import android.os.connectivity.GpsBatteryStats; import android.os.connectivity.WifiActivityEnergyInfo; import android.os.connectivity.WifiBatteryStats; import android.provider.Settings; +import android.telephony.Annotation.NetworkType; import android.telephony.CellSignalStrength; +import android.telephony.CellSignalStrengthLte; +import android.telephony.CellSignalStrengthNr; import android.telephony.DataConnectionRealTimeInfo; import android.telephony.ModemActivityInfo; import android.telephony.ServiceState; +import android.telephony.ServiceState.RegState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -935,6 +939,119 @@ public class BatteryStatsImpl extends BatteryStats { final StopwatchTimer[] mPhoneDataConnectionsTimer = new StopwatchTimer[NUM_DATA_CONNECTION_TYPES]; + @RadioAccessTechnology + int mActiveRat = RADIO_ACCESS_TECHNOLOGY_OTHER; + + private static class RadioAccessTechnologyBatteryStats { + /** + * This RAT is currently being used. + */ + private boolean mActive = false; + /** + * Current active frequency range for this RAT. + */ + @ServiceState.FrequencyRange + private int mFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN; + /** + * Current signal strength for this RAT. + */ + private int mSignalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + /** + * Timers for each combination of frequency range and signal strength. + */ + public final StopwatchTimer[][] perStateTimers; + + RadioAccessTechnologyBatteryStats(int freqCount, Clock clock, TimeBase timeBase) { + perStateTimers = + new StopwatchTimer[freqCount][CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS]; + for (int i = 0; i < freqCount; i++) { + for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) { + perStateTimers[i][j] = new StopwatchTimer(clock, null, -1, null, timeBase); + } + } + } + + /** + * Note this RAT is currently being used. + */ + public void noteActive(boolean active, long elapsedRealtimeMs) { + if (mActive == active) return; + mActive = active; + if (mActive) { + perStateTimers[mFrequencyRange][mSignalStrength].startRunningLocked( + elapsedRealtimeMs); + } else { + perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked( + elapsedRealtimeMs); + } + } + + /** + * Note current frequency range has changed. + */ + public void noteFrequencyRange(@ServiceState.FrequencyRange int frequencyRange, + long elapsedRealtimeMs) { + if (mFrequencyRange == frequencyRange) return; + + if (!mActive) { + // RAT not in use, note the frequency change and move on. + mFrequencyRange = frequencyRange; + return; + } + perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs); + perStateTimers[frequencyRange][mSignalStrength].startRunningLocked(elapsedRealtimeMs); + mFrequencyRange = frequencyRange; + } + + /** + * Note current signal strength has changed. + */ + public void noteSignalStrength(int signalStrength, long elapsedRealtimeMs) { + if (mSignalStrength == signalStrength) return; + + if (!mActive) { + // RAT not in use, note the signal strength change and move on. + mSignalStrength = signalStrength; + return; + } + perStateTimers[mFrequencyRange][mSignalStrength].stopRunningLocked(elapsedRealtimeMs); + perStateTimers[mFrequencyRange][signalStrength].startRunningLocked(elapsedRealtimeMs); + mSignalStrength = signalStrength; + } + + /** + * Reset display timers. + */ + public void reset(long elapsedRealtimeUs) { + final int size = perStateTimers.length; + for (int i = 0; i < size; i++) { + for (int j = 0; j < CellSignalStrength.NUM_SIGNAL_STRENGTH_BINS; j++) { + perStateTimers[i][j].reset(false, elapsedRealtimeUs); + } + } + } + } + + /** + * Number of frequency ranges, keep in sync with {@link ServiceState.FrequencyRange} + */ + private static final int NR_FREQUENCY_COUNT = 5; + + RadioAccessTechnologyBatteryStats[] mPerRatBatteryStats = + new RadioAccessTechnologyBatteryStats[RADIO_ACCESS_TECHNOLOGY_COUNT]; + + @GuardedBy("this") + private RadioAccessTechnologyBatteryStats getRatBatteryStatsLocked( + @RadioAccessTechnology int rat) { + RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat]; + if (stats == null) { + final int freqCount = rat == RADIO_ACCESS_TECHNOLOGY_NR ? NR_FREQUENCY_COUNT : 1; + stats = new RadioAccessTechnologyBatteryStats(freqCount, mClock, mOnBatteryTimeBase); + mPerRatBatteryStats[rat] = stats; + } + return stats; + } + final LongSamplingCounter[] mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES]; @@ -1896,17 +2013,15 @@ public class BatteryStatsImpl extends BatteryStats { private final TimeBase mTimeBase; private final LongArrayMultiStateCounter mCounter; - private TimeInFreqMultiStateCounter(TimeBase timeBase, Parcel in, long timestampMs) { - mTimeBase = timeBase; - mCounter = LongArrayMultiStateCounter.CREATOR.createFromParcel(in); - mCounter.setEnabled(mTimeBase.isRunning(), timestampMs); - timeBase.add(this); + private TimeInFreqMultiStateCounter(TimeBase timeBase, int stateCount, int cpuFreqCount, + long timestampMs) { + this(timeBase, new LongArrayMultiStateCounter(stateCount, cpuFreqCount), timestampMs); } - private TimeInFreqMultiStateCounter(TimeBase timeBase, int stateCount, int cpuFreqCount, + private TimeInFreqMultiStateCounter(TimeBase timeBase, LongArrayMultiStateCounter counter, long timestampMs) { mTimeBase = timeBase; - mCounter = new LongArrayMultiStateCounter(stateCount, cpuFreqCount); + mCounter = counter; mCounter.setEnabled(mTimeBase.isRunning(), timestampMs); timeBase.add(this); } @@ -1915,6 +2030,19 @@ public class BatteryStatsImpl extends BatteryStats { mCounter.writeToParcel(out, 0); } + @Nullable + private static TimeInFreqMultiStateCounter readFromParcel(Parcel in, TimeBase timeBase, + int stateCount, int cpuFreqCount, long timestampMs) { + // Read the object from the Parcel, whether it's usable or not + LongArrayMultiStateCounter counter = + LongArrayMultiStateCounter.CREATOR.createFromParcel(in); + if (counter.getStateCount() != stateCount + || counter.getArrayLength() != cpuFreqCount) { + return null; + } + return new TimeInFreqMultiStateCounter(timeBase, counter, timestampMs); + } + @Override public void onTimeStarted(long elapsedRealtimeUs, long baseUptimeUs, long baseRealtimeUs) { mCounter.setEnabled(true, elapsedRealtimeUs / 1000); @@ -5886,6 +6014,10 @@ public class BatteryStatsImpl extends BatteryStats { + Integer.toHexString(mHistoryCur.states)); addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs); mMobileRadioPowerState = powerState; + + // Inform current RatBatteryStats that the modem active state might have changed. + getRatBatteryStatsLocked(mActiveRat).noteActive(active, elapsedRealtimeMs); + if (active) { mMobileRadioActiveTimer.startRunningLocked(elapsedRealtimeMs); mMobileRadioActivePerAppTimer.startRunningLocked(elapsedRealtimeMs); @@ -6307,21 +6439,86 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void notePhoneSignalStrengthLocked(SignalStrength signalStrength, long elapsedRealtimeMs, long uptimeMs) { - // Bin the strength. - int bin = signalStrength.getLevel(); - updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, bin, + final int overallSignalStrength = signalStrength.getLevel(); + final SparseIntArray perRatSignalStrength = new SparseIntArray( + BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT); + + // Extract signal strength level for each RAT. + final List<CellSignalStrength> cellSignalStrengths = + signalStrength.getCellSignalStrengths(); + final int size = cellSignalStrengths.size(); + for (int i = 0; i < size; i++) { + CellSignalStrength cellSignalStrength = cellSignalStrengths.get(i); + // Map each CellSignalStrength to a BatteryStats.RadioAccessTechnology + final int ratType; + final int level; + if (cellSignalStrength instanceof CellSignalStrengthNr) { + ratType = RADIO_ACCESS_TECHNOLOGY_NR; + level = cellSignalStrength.getLevel(); + } else if (cellSignalStrength instanceof CellSignalStrengthLte) { + ratType = RADIO_ACCESS_TECHNOLOGY_LTE; + level = cellSignalStrength.getLevel(); + } else { + ratType = RADIO_ACCESS_TECHNOLOGY_OTHER; + level = cellSignalStrength.getLevel(); + } + + // According to SignalStrength#getCellSignalStrengths(), multiple of the same + // cellSignalStrength can be present. Just take the highest level one for each RAT. + if (perRatSignalStrength.get(ratType, -1) < level) { + perRatSignalStrength.put(ratType, level); + } + } + + notePhoneSignalStrengthLocked(overallSignalStrength, perRatSignalStrength, + elapsedRealtimeMs, uptimeMs); + } + + /** + * Note phone signal strength change, including per RAT signal strength. + * + * @param signalStrength overall signal strength {@see SignalStrength#getLevel()} + * @param perRatSignalStrength signal strength of available RATs + */ + @GuardedBy("this") + public void notePhoneSignalStrengthLocked(int signalStrength, + SparseIntArray perRatSignalStrength) { + notePhoneSignalStrengthLocked(signalStrength, perRatSignalStrength, + mClock.elapsedRealtime(), mClock.uptimeMillis()); + } + + /** + * Note phone signal strength change, including per RAT signal strength. + * + * @param signalStrength overall signal strength {@see SignalStrength#getLevel()} + * @param perRatSignalStrength signal strength of available RATs + */ + @GuardedBy("this") + public void notePhoneSignalStrengthLocked(int signalStrength, + SparseIntArray perRatSignalStrength, + long elapsedRealtimeMs, long uptimeMs) { + // Note each RAT's signal strength. + final int size = perRatSignalStrength.size(); + for (int i = 0; i < size; i++) { + final int rat = perRatSignalStrength.keyAt(i); + final int ratSignalStrength = perRatSignalStrength.valueAt(i); + getRatBatteryStatsLocked(rat).noteSignalStrength(ratSignalStrength, elapsedRealtimeMs); + } + updateAllPhoneStateLocked(mPhoneServiceStateRaw, mPhoneSimStateRaw, signalStrength, elapsedRealtimeMs, uptimeMs); } @UnsupportedAppUsage @GuardedBy("this") - public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType) { - notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, + public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData, + @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency) { + notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, nrFrequency, mClock.elapsedRealtime(), mClock.uptimeMillis()); } @GuardedBy("this") - public void notePhoneDataConnectionStateLocked(int dataType, boolean hasData, int serviceType, + public void notePhoneDataConnectionStateLocked(@NetworkType int dataType, boolean hasData, + @RegState int serviceType, @ServiceState.FrequencyRange int nrFrequency, long elapsedRealtimeMs, long uptimeMs) { // BatteryStats uses 0 to represent no network type. // Telephony does not have a concept of no network type, and uses 0 to represent unknown. @@ -6344,6 +6541,13 @@ public class BatteryStatsImpl extends BatteryStats { } } } + + final int newRat = mapNetworkTypeToRadioAccessTechnology(bin); + if (newRat == RADIO_ACCESS_TECHNOLOGY_NR) { + // Note possible frequency change for the NR RAT. + getRatBatteryStatsLocked(newRat).noteFrequencyRange(nrFrequency, elapsedRealtimeMs); + } + if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData); if (mPhoneDataConnectionType != bin) { mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK) @@ -6357,6 +6561,45 @@ public class BatteryStatsImpl extends BatteryStats { } mPhoneDataConnectionType = bin; mPhoneDataConnectionsTimer[bin].startRunningLocked(elapsedRealtimeMs); + + if (mActiveRat != newRat) { + getRatBatteryStatsLocked(mActiveRat).noteActive(false, elapsedRealtimeMs); + mActiveRat = newRat; + } + final boolean modemActive = mMobileRadioActiveTimer.isRunningLocked(); + getRatBatteryStatsLocked(newRat).noteActive(modemActive, elapsedRealtimeMs); + } + } + + @RadioAccessTechnology + private static int mapNetworkTypeToRadioAccessTechnology(@NetworkType int dataType) { + switch (dataType) { + case TelephonyManager.NETWORK_TYPE_NR: + return RADIO_ACCESS_TECHNOLOGY_NR; + case TelephonyManager.NETWORK_TYPE_LTE: + return RADIO_ACCESS_TECHNOLOGY_LTE; + case TelephonyManager.NETWORK_TYPE_UNKNOWN: //fallthrough + case TelephonyManager.NETWORK_TYPE_GPRS: //fallthrough + case TelephonyManager.NETWORK_TYPE_EDGE: //fallthrough + case TelephonyManager.NETWORK_TYPE_UMTS: //fallthrough + case TelephonyManager.NETWORK_TYPE_CDMA: //fallthrough + case TelephonyManager.NETWORK_TYPE_EVDO_0: //fallthrough + case TelephonyManager.NETWORK_TYPE_EVDO_A: //fallthrough + case TelephonyManager.NETWORK_TYPE_1xRTT: //fallthrough + case TelephonyManager.NETWORK_TYPE_HSDPA: //fallthrough + case TelephonyManager.NETWORK_TYPE_HSUPA: //fallthrough + case TelephonyManager.NETWORK_TYPE_HSPA: //fallthrough + case TelephonyManager.NETWORK_TYPE_IDEN: //fallthrough + case TelephonyManager.NETWORK_TYPE_EVDO_B: //fallthrough + case TelephonyManager.NETWORK_TYPE_EHRPD: //fallthrough + case TelephonyManager.NETWORK_TYPE_HSPAP: //fallthrough + case TelephonyManager.NETWORK_TYPE_GSM: //fallthrough + case TelephonyManager.NETWORK_TYPE_TD_SCDMA: //fallthrough + case TelephonyManager.NETWORK_TYPE_IWLAN: //fallthrough + return RADIO_ACCESS_TECHNOLOGY_OTHER; + default: + Slog.w(TAG, "Unhandled NetworkType (" + dataType + "), mapping to OTHER"); + return RADIO_ACCESS_TECHNOLOGY_OTHER; } } @@ -7731,6 +7974,23 @@ public class BatteryStatsImpl extends BatteryStats { return mPhoneDataConnectionsTimer[dataType]; } + @Override public long getActiveRadioDurationMs(@RadioAccessTechnology int rat, + @ServiceState.FrequencyRange int frequencyRange, int signalStrength, + long elapsedRealtimeMs) { + final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[rat]; + if (stats == null) return 0L; + + final int freqCount = stats.perStateTimers.length; + if (frequencyRange < 0 || frequencyRange >= freqCount) return 0L; + + final StopwatchTimer[] strengthTimers = stats.perStateTimers[frequencyRange]; + final int strengthCount = strengthTimers.length; + if (signalStrength < 0 || signalStrength >= strengthCount) return 0L; + + return stats.perStateTimers[frequencyRange][signalStrength].getTotalTimeLocked( + elapsedRealtimeMs * 1000, STATS_SINCE_CHARGED) / 1000; + } + @UnsupportedAppUsage @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) { return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which); @@ -10535,25 +10795,18 @@ public class BatteryStatsImpl extends BatteryStats { stateCount = in.readInt(); if (stateCount != 0) { - // Read the object from the Parcel, whether it's usable or not - TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter( - mBsi.mOnBatteryTimeBase, in, timestampMs); - if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) { - mProcStateTimeMs = counter; - } + mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in, + mBsi.mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT, + mBsi.getCpuFreqCount(), mBsi.mClock.elapsedRealtime()); } else { mProcStateTimeMs = null; } stateCount = in.readInt(); if (stateCount != 0) { - // Read the object from the Parcel, whether it's usable or not - TimeInFreqMultiStateCounter counter = - new TimeInFreqMultiStateCounter( - mBsi.mOnBatteryScreenOffTimeBase, in, timestampMs); - if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) { - mProcStateScreenOffTimeMs = counter; - } + mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in, + mBsi.mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT, + mBsi.getCpuFreqCount(), mBsi.mClock.elapsedRealtime()); } else { mProcStateScreenOffTimeMs = null; } @@ -12553,6 +12806,11 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkByteActivityCounters[i].reset(false, elapsedRealtimeUs); mNetworkPacketActivityCounters[i].reset(false, elapsedRealtimeUs); } + for (int i = 0; i < RADIO_ACCESS_TECHNOLOGY_COUNT; i++) { + final RadioAccessTechnologyBatteryStats stats = mPerRatBatteryStats[i]; + if (stats == null) continue; + stats.reset(elapsedRealtimeUs); + } mMobileRadioActiveTimer.reset(false, elapsedRealtimeUs); mMobileRadioActivePerAppTimer.reset(false, elapsedRealtimeUs); mMobileRadioActiveAdjustedTime.reset(false, elapsedRealtimeUs); @@ -16107,6 +16365,11 @@ public class BatteryStatsImpl extends BatteryStats { BATTERY_CHARGED_DELAY_MS = delay >= 0 ? delay : mParser.getInt( KEY_BATTERY_CHARGED_DELAY_MS, DEFAULT_BATTERY_CHARGED_DELAY_MS); + + if (mHandler.hasCallbacks(mDeferSetCharging)) { + mHandler.removeCallbacks(mDeferSetCharging); + mHandler.postDelayed(mDeferSetCharging, BATTERY_CHARGED_DELAY_MS); + } } private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) { @@ -16906,12 +17169,10 @@ public class BatteryStatsImpl extends BatteryStats { stateCount = in.readInt(); if (stateCount != 0) { - // Read the object from the Parcel, whether it's usable or not - TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter( - mOnBatteryTimeBase, in, mClock.elapsedRealtime()); - if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) { - u.mProcStateTimeMs = counter; - } + detachIfNotNull(u.mProcStateTimeMs); + u.mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in, + mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT, + getCpuFreqCount(), mClock.elapsedRealtime()); } detachIfNotNull(u.mProcStateScreenOffTimeMs); @@ -16920,13 +17181,9 @@ public class BatteryStatsImpl extends BatteryStats { stateCount = in.readInt(); if (stateCount != 0) { detachIfNotNull(u.mProcStateScreenOffTimeMs); - // Read the object from the Parcel, whether it's usable or not - TimeInFreqMultiStateCounter counter = - new TimeInFreqMultiStateCounter( - mOnBatteryScreenOffTimeBase, in, mClock.elapsedRealtime()); - if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) { - u.mProcStateScreenOffTimeMs = counter; - } + u.mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in, + mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT, + getCpuFreqCount(), mClock.elapsedRealtime()); } if (in.readInt() != 0) { diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index be91aaca5d39..0a29fc5285a5 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -1159,6 +1159,17 @@ public class BinderCallsStats implements BinderInternal.Observer { : Integer.compare(a.transactionCode, b.transactionCode); } + /** @hide */ + public static void startForBluetooth(Context context) { + new BinderCallsStats.SettingsObserver( + context, + new BinderCallsStats( + new BinderCallsStats.Injector(), + com.android.internal.os.BinderLatencyProto.Dims.BLUETOOTH)); + + } + + /** * Settings observer for other processes (not system_server). diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index a50282ee0aa6..2925341cd948 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -285,6 +285,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private Insets mBackgroundInsets = Insets.NONE; private Insets mLastBackgroundInsets = Insets.NONE; private boolean mDrawLegacyNavigationBarBackground; + private boolean mDrawLegacyNavigationBarBackgroundHandled; private PendingInsetsController mPendingInsetsController = new PendingInsetsController(); @@ -1035,10 +1036,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind public void onSystemBarAppearanceChanged(@WindowInsetsController.Appearance int appearance) { updateColorViews(null /* insets */, true /* animate */); if (mWindow != null) { - final Window.Callback callback = mWindow.getCallback(); - if (callback != null) { - callback.onSystemBarAppearanceChanged(appearance); - } + mWindow.dispatchOnSystemBarAppearanceChanged(appearance); } } @@ -1174,6 +1172,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0; if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) { + mDrawLegacyNavigationBarBackgroundHandled = + mWindow.onDrawLegacyNavigationBarBackgroundChanged( + mDrawLegacyNavigationBarBackground); if (viewRoot != null) { viewRoot.requestInvalidateRootRenderNode(); } @@ -1266,7 +1267,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } } - if (forceConsumingNavBar) { + if (forceConsumingNavBar && !mDrawLegacyNavigationBarBackgroundHandled) { mBackgroundInsets = Insets.of(mLastLeftInset, 0, mLastRightInset, mLastBottomInset); } else { mBackgroundInsets = Insets.NONE; @@ -2491,7 +2492,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind } private void drawLegacyNavigationBarBackground(RecordingCanvas canvas) { - if (!mDrawLegacyNavigationBarBackground) { + if (!mDrawLegacyNavigationBarBackground || mDrawLegacyNavigationBarBackgroundHandled) { return; } View v = mNavigationColorViewState.view; diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index faea7706ee14..ece6f2f3571b 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -29,7 +29,6 @@ import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN; import static android.view.WindowManager.TRANSIT_OPEN; -import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -915,7 +914,7 @@ public class TransitionAnimation { * animation. */ public HardwareBuffer createCrossProfileAppsThumbnail( - @DrawableRes int thumbnailDrawableRes, Rect frame) { + Drawable thumbnailDrawable, Rect frame) { final int width = frame.width(); final int height = frame.height(); @@ -924,14 +923,13 @@ public class TransitionAnimation { canvas.drawColor(Color.argb(0.6f, 0, 0, 0)); final int thumbnailSize = mContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.cross_profile_apps_thumbnail_size); - final Drawable drawable = mContext.getDrawable(thumbnailDrawableRes); - drawable.setBounds( + thumbnailDrawable.setBounds( (width - thumbnailSize) / 2, (height - thumbnailSize) / 2, (width + thumbnailSize) / 2, (height + thumbnailSize) / 2); - drawable.setTint(mContext.getColor(android.R.color.white)); - drawable.draw(canvas); + thumbnailDrawable.setTint(mContext.getColor(android.R.color.white)); + thumbnailDrawable.draw(canvas); picture.endRecording(); return Bitmap.createBitmap(picture).getHardwareBuffer(); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index a5cf7ce1d37c..23ebc9f94915 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -20,6 +20,7 @@ import android.app.ITransientNotificationCallback; import android.content.ComponentName; import android.graphics.drawable.Icon; import android.graphics.Rect; +import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.fingerprint.IUdfpsHbmListener; @@ -166,6 +167,8 @@ oneway interface IStatusBar * Used to hide the authentication dialog, e.g. when the application cancels authentication. */ void hideAuthenticationDialog(); + /* Used to notify the biometric service of events that occur outside of an operation. */ + void setBiometicContextListener(in IBiometricContextListener listener); /** * Sets an instance of IUdfpsHbmListener for UdfpsController. diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index accb98645599..f28325e3cc51 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -20,6 +20,7 @@ import android.app.Notification; import android.content.ComponentName; import android.graphics.drawable.Icon; import android.graphics.Rect; +import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.fingerprint.IUdfpsHbmListener; @@ -125,6 +126,8 @@ interface IStatusBarService void onBiometricError(int modality, int error, int vendorCode); // Used to hide the authentication dialog, e.g. when the application cancels authentication void hideAuthenticationDialog(); + // Used to notify the biometric service of events that occur outside of an operation. + void setBiometicContextListener(in IBiometricContextListener listener); /** * Sets an instance of IUdfpsHbmListener for UdfpsController. diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index da24832f9d84..d2bc3442ed36 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -37,7 +37,8 @@ import com.android.internal.view.InlineSuggestionsRequestInfo; */ oneway interface IInputMethod { void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, - int configChanges, boolean stylusHwSupported); + int configChanges, boolean stylusHwSupported, + boolean shouldShowImeSwitcherWhenImeIsShown); void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo, in IInlineSuggestionsRequestCallback cb); @@ -47,7 +48,10 @@ oneway interface IInputMethod { void unbindInput(); void startInput(in IBinder startInputToken, in IInputContext inputContext, - in EditorInfo attribute, boolean restarting); + in EditorInfo attribute, boolean restarting, + boolean shouldShowImeSwitcherWhenImeIsShown); + + void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown); void createSession(in InputChannel channel, IInputSessionCallback callback); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 430d84ebb3e1..8bb9a0a0d6ff 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -195,7 +195,6 @@ cc_library_shared { "android_util_FileObserver.cpp", "android/opengl/poly_clip.cpp", // TODO: .arm "android/opengl/util.cpp", - "android_server_NetworkManagementSocketTagger.cpp", "android_ddm_DdmHandleNativeHeap.cpp", "android_backup_BackupDataInput.cpp", "android_backup_BackupDataOutput.cpp", @@ -310,6 +309,8 @@ cc_library_shared { "libdl_android", "libtimeinstate", "server_configurable_flags", + // TODO: delete when ConnectivityT moves to APEX. + "libframework-connectivity-tiramisu-jni", ], export_shared_lib_headers: [ // our headers include libnativewindow's public headers diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f4296becf484..cde71cf6b30f 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -164,7 +164,6 @@ extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_Hyphenator(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); -extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env); extern int register_android_backup_BackupDataInput(JNIEnv *env); extern int register_android_backup_BackupDataOutput(JNIEnv *env); extern int register_android_backup_FileBackupHelperBase(JNIEnv *env); @@ -1623,7 +1622,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_media_midi), REG_JNI(register_android_opengl_classes), - REG_JNI(register_android_server_NetworkManagementSocketTagger), REG_JNI(register_android_ddm_DdmHandleNativeHeap), REG_JNI(register_android_backup_BackupDataInput), REG_JNI(register_android_backup_BackupDataOutput), diff --git a/core/jni/android/opengl/OWNERS b/core/jni/android/opengl/OWNERS new file mode 100644 index 000000000000..ce4b9075d59c --- /dev/null +++ b/core/jni/android/opengl/OWNERS @@ -0,0 +1 @@ +file:/graphics/java/android/graphics/OWNERS diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp index 82601baee914..d8522658d747 100644 --- a/core/jni/android/opengl/util.cpp +++ b/core/jni/android/opengl/util.cpp @@ -643,6 +643,8 @@ static bool checkInternalFormat(int32_t bitmapFormat, int internalformat, int ty return (type == GL_UNSIGNED_SHORT_5_6_5 && internalformat == GL_RGB); case ANDROID_BITMAP_FORMAT_RGBA_F16: return (type == GL_HALF_FLOAT && internalformat == GL_RGBA16F); + case ANDROID_BITMAP_FORMAT_RGBA_1010102: + return (type == GL_UNSIGNED_INT_2_10_10_10_REV && internalformat == GL_RGB10_A2); default: break; } @@ -676,6 +678,8 @@ static int getInternalFormat(int32_t bitmapFormat) { return GL_RGB; case ANDROID_BITMAP_FORMAT_RGBA_F16: return GL_RGBA16F; + case ANDROID_BITMAP_FORMAT_RGBA_1010102: + return GL_RGB10_A2; default: return -1; } @@ -693,6 +697,8 @@ static int getType(int32_t bitmapFormat) { return GL_UNSIGNED_SHORT_5_6_5; case ANDROID_BITMAP_FORMAT_RGBA_F16: return GL_HALF_FLOAT; + case ANDROID_BITMAP_FORMAT_RGBA_1010102: + return GL_UNSIGNED_INT_2_10_10_10_REV; default: return -1; } diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index d039bcff3b26..78b403c39a17 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -133,6 +133,18 @@ nativeClassInit (JNIEnv *_env, jclass _this) MakeGlobalRefOrDie(_env, _env->CallObjectMethod(empty.get(), stringOffsets.intern)); } +uint64_t htonll(uint64_t ll) { + constexpr uint32_t kBytesToTest = 0x12345678; + constexpr uint8_t kFirstByte = (const uint8_t &)kBytesToTest; + constexpr bool kIsLittleEndian = kFirstByte == 0x78; + + if constexpr (kIsLittleEndian) { + return static_cast<uint64_t>(htonl(ll & 0xffffffff)) << 32 | htonl(ll >> 32); + } else { + return ll; + } +} + static jstring getJavaInternedString(JNIEnv *env, const String8 &string) { if (string == "") { return gStringOffsets.emptyString; @@ -193,7 +205,8 @@ translateNativeSensorToJavaSensor(JNIEnv *env, jobject sensor, const Sensor& nat int32_t id = nativeSensor.getId(); env->CallVoidMethod(sensor, sensorOffsets.setId, id); Sensor::uuid_t uuid = nativeSensor.getUuid(); - env->CallVoidMethod(sensor, sensorOffsets.setUuid, id, uuid.i64[0], uuid.i64[1]); + env->CallVoidMethod(sensor, sensorOffsets.setUuid, htonll(uuid.i64[0]), + htonll(uuid.i64[1])); } return sensor; } diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index e13b78868a2c..edc8c5b99ebe 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -204,6 +204,12 @@ jclass gClsAudioRecordRoutingProxy; jclass gAudioProfileClass; jmethodID gAudioProfileCstor; +static struct { + jfieldID mSamplingRates; + jfieldID mChannelMasks; + jfieldID mChannelIndexMasks; + jfieldID mEncapsulationType; +} gAudioProfileFields; jclass gVibratorClass; static struct { @@ -1288,7 +1294,7 @@ static jint convertAudioProfileFromNative(JNIEnv *env, jobject *jAudioProfile, audioFormatFromNative(nAudioProfile->format), jSamplingRates.get(), jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType); - if (jAudioProfile == nullptr) { + if (*jAudioProfile == nullptr) { return AUDIO_JAVA_ERROR; } @@ -1340,81 +1346,50 @@ static jint convertAudioPortFromNative(JNIEnv *env, jobject *jAudioPort, jStatus = (jint)AUDIO_JAVA_ERROR; goto exit; } - for (size_t i = 0; i < nAudioPort->num_audio_profiles; ++i) { - size_t numPositionMasks = 0; - size_t numIndexMasks = 0; - // count up how many masks are positional and indexed - for (size_t index = 0; index < nAudioPort->audio_profiles[i].num_channel_masks; index++) { - const audio_channel_mask_t mask = nAudioPort->audio_profiles[i].channel_masks[index]; - if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) { - numIndexMasks++; - } else { - numPositionMasks++; - } - } - ScopedLocalRef<jintArray> jSamplingRates(env, - env->NewIntArray(nAudioPort->audio_profiles[i] - .num_sample_rates)); - ScopedLocalRef<jintArray> jChannelMasks(env, env->NewIntArray(numPositionMasks)); - ScopedLocalRef<jintArray> jChannelIndexMasks(env, env->NewIntArray(numIndexMasks)); - if (!jSamplingRates.get() || !jChannelMasks.get() || !jChannelIndexMasks.get()) { + for (size_t i = 0; i < nAudioPort->num_audio_profiles; ++i) { + jobject jAudioProfile = nullptr; + jStatus = convertAudioProfileFromNative(env, &jAudioProfile, &nAudioPort->audio_profiles[i], + useInMask); + if (jStatus != NO_ERROR) { jStatus = (jint)AUDIO_JAVA_ERROR; goto exit; } + env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile); - if (nAudioPort->audio_profiles[i].num_sample_rates) { - env->SetIntArrayRegion(jSamplingRates.get(), 0 /*start*/, - nAudioPort->audio_profiles[i].num_sample_rates, - (jint *)nAudioPort->audio_profiles[i].sample_rates); - } - - // put the masks in the output arrays - for (size_t maskIndex = 0, posMaskIndex = 0, indexedMaskIndex = 0; - maskIndex < nAudioPort->audio_profiles[i].num_channel_masks; maskIndex++) { - const audio_channel_mask_t mask = - nAudioPort->audio_profiles[i].channel_masks[maskIndex]; - if (audio_channel_mask_get_representation(mask) == AUDIO_CHANNEL_REPRESENTATION_INDEX) { - jint jMask = audio_channel_mask_get_bits(mask); - env->SetIntArrayRegion(jChannelIndexMasks.get(), indexedMaskIndex++, 1, &jMask); - } else { - jint jMask = - useInMask ? inChannelMaskFromNative(mask) : outChannelMaskFromNative(mask); - env->SetIntArrayRegion(jChannelMasks.get(), posMaskIndex++, 1, &jMask); - } - } - - int encapsulationType; - if (audioEncapsulationTypeFromNative(nAudioPort->audio_profiles[i].encapsulation_type, - &encapsulationType) != NO_ERROR) { - ALOGW("Unknown encapsualtion type for JAVA API: %u", - nAudioPort->audio_profiles[i].encapsulation_type); - continue; - } - - ScopedLocalRef<jobject> - jAudioProfile(env, - env->NewObject(gAudioProfileClass, gAudioProfileCstor, - audioFormatFromNative( - nAudioPort->audio_profiles[i].format), - jSamplingRates.get(), jChannelMasks.get(), - jChannelIndexMasks.get(), encapsulationType)); - if (jAudioProfile == nullptr) { - jStatus = (jint)AUDIO_JAVA_ERROR; - goto exit; - } - env->CallBooleanMethod(jAudioProfiles, gArrayListMethods.add, jAudioProfile.get()); if (nAudioPort->audio_profiles[i].format == AUDIO_FORMAT_PCM_FLOAT) { hasFloat = true; } else if (jPcmFloatProfileFromExtendedInteger.get() == nullptr && audio_is_linear_pcm(nAudioPort->audio_profiles[i].format) && audio_bytes_per_sample(nAudioPort->audio_profiles[i].format) > 2) { + ScopedLocalRef<jintArray> + jSamplingRates(env, + (jintArray) + env->GetObjectField(jAudioProfile, + gAudioProfileFields.mSamplingRates)); + ScopedLocalRef<jintArray> + jChannelMasks(env, + (jintArray) + env->GetObjectField(jAudioProfile, + gAudioProfileFields.mChannelMasks)); + ScopedLocalRef<jintArray> + jChannelIndexMasks(env, + (jintArray)env->GetObjectField(jAudioProfile, + gAudioProfileFields + .mChannelIndexMasks)); + int encapsulationType = + env->GetIntField(jAudioProfile, gAudioProfileFields.mEncapsulationType); + jPcmFloatProfileFromExtendedInteger.reset( env->NewObject(gAudioProfileClass, gAudioProfileCstor, audioFormatFromNative(AUDIO_FORMAT_PCM_FLOAT), jSamplingRates.get(), jChannelMasks.get(), jChannelIndexMasks.get(), encapsulationType)); } + + if (jAudioProfile != nullptr) { + env->DeleteLocalRef(jAudioProfile); + } } if (!hasFloat && jPcmFloatProfileFromExtendedInteger.get() != nullptr) { // R and earlier compatibility - add ENCODING_PCM_FLOAT to the end @@ -3285,6 +3260,14 @@ int register_android_media_AudioSystem(JNIEnv *env) jclass audioProfileClass = FindClassOrDie(env, "android/media/AudioProfile"); gAudioProfileClass = MakeGlobalRefOrDie(env, audioProfileClass); gAudioProfileCstor = GetMethodIDOrDie(env, audioProfileClass, "<init>", "(I[I[I[II)V"); + gAudioProfileFields.mSamplingRates = + GetFieldIDOrDie(env, audioProfileClass, "mSamplingRates", "[I"); + gAudioProfileFields.mChannelMasks = + GetFieldIDOrDie(env, audioProfileClass, "mChannelMasks", "[I"); + gAudioProfileFields.mChannelIndexMasks = + GetFieldIDOrDie(env, audioProfileClass, "mChannelIndexMasks", "[I"); + gAudioProfileFields.mEncapsulationType = + GetFieldIDOrDie(env, audioProfileClass, "mEncapsulationType", "I"); jclass audioDescriptorClass = FindClassOrDie(env, "android/media/AudioDescriptor"); gAudioDescriptorClass = MakeGlobalRefOrDie(env, audioDescriptorClass); diff --git a/core/jni/android_server_NetworkManagementSocketTagger.cpp b/core/jni/android_server_NetworkManagementSocketTagger.cpp deleted file mode 100644 index 9734ab9b9e1d..000000000000 --- a/core/jni/android_server_NetworkManagementSocketTagger.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2011, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "NMST_QTagUidNative" - -#include <android/multinetwork.h> -#include <cutils/qtaguid.h> -#include <errno.h> -#include <fcntl.h> -#include <nativehelper/JNIPlatformHelp.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <utils/Log.h> -#include <utils/misc.h> - -#include "jni.h" - -namespace android { - -static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor, - jint tagNum, jint uid) { - int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor); - - if (env->ExceptionCheck()) { - ALOGE("Can't get FileDescriptor num"); - return (jint)-1; - } - - int res = android_tag_socket_with_uid(userFd, tagNum, uid); - if (res < 0) { - return (jint)-errno; - } - return (jint)res; -} - -static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) { - int userFd = jniGetFDFromFileDescriptor(env, fileDescriptor); - - if (env->ExceptionCheck()) { - ALOGE("Can't get FileDescriptor num"); - return (jint)-1; - } - - int res = android_untag_socket(userFd); - if (res < 0) { - return (jint)-errno; - } - return (jint)res; -} - -static const JNINativeMethod gQTagUidMethods[] = { - { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*)tagSocketFd}, - { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*)untagSocketFd}, -}; - -int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) { - return jniRegisterNativeMethods(env, "com/android/server/NetworkManagementSocketTagger", gQTagUidMethods, NELEM(gQTagUidMethods)); -} - -}; diff --git a/core/proto/android/server/Android.bp b/core/proto/android/server/Android.bp new file mode 100644 index 000000000000..362daa73ff14 --- /dev/null +++ b/core/proto/android/server/Android.bp @@ -0,0 +1,28 @@ +// +// 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_applicable_licenses: ["Android-Apache-2.0"], +} + +filegroup { + name: "srcs_bluetooth_manager_service_proto", + srcs: [ + "bluetooth_manager_service.proto", + ], + visibility: ["//packages/modules/Bluetooth:__subpackages__"], +} + diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c9bd22210a77..c086098444da 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -719,6 +719,7 @@ <protected-broadcast android:name="android.safetycenter.action.REFRESH_SAFETY_SOURCES" /> <protected-broadcast android:name="android.app.action.DEVICE_POLICY_RESOURCE_UPDATED" /> <protected-broadcast android:name="android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER" /> + <protected-broadcast android:name="android.service.autofill.action.DELAYED_FILL" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> @@ -976,61 +977,6 @@ android:permissionFlags="softRestricted|immutablyRestricted" android:protectionLevel="dangerous" /> - <!-- Required to be able to read audio files from shared storage. - <p>Protection level: dangerous --> - <permission-group android:name="android.permission-group.READ_MEDIA_AURAL" - android:icon="@drawable/perm_group_read_media_aural" - android:label="@string/permgrouplab_readMediaAural" - android:description="@string/permgroupdesc_readMediaAural" - android:priority="950" /> - - <!-- Allows an application to read audio files from external storage. - <p>This permission is enforced starting in API level - {@link android.os.Build.VERSION_CODES#TIRAMISU}. - For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code - targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission - must not be used and the READ_EXTERNAL_STORAGE permission must be used instead. - <p>Protection level: dangerous --> - <permission android:name="android.permission.READ_MEDIA_AUDIO" - android:permissionGroup="android.permission-group.UNDEFINED" - android:label="@string/permlab_readMediaAudio" - android:description="@string/permdesc_readMediaAudio" - android:protectionLevel="dangerous" /> - - <!-- Required to be able to read image and video files from shared storage. - <p>Protection level: dangerous --> - <permission-group android:name="android.permission-group.READ_MEDIA_VISUAL" - android:icon="@drawable/perm_group_read_media_visual" - android:label="@string/permgrouplab_readMediaVisual" - android:description="@string/permgroupdesc_readMediaVisual" - android:priority="1000" /> - - <!-- Allows an application to read audio files from external storage. - <p>This permission is enforced starting in API level - {@link android.os.Build.VERSION_CODES#TIRAMISU}. - For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code - targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission - must not be used and the READ_EXTERNAL_STORAGE permission must be used instead. - <p>Protection level: dangerous --> - <permission android:name="android.permission.READ_MEDIA_VIDEO" - android:permissionGroup="android.permission-group.UNDEFINED" - android:label="@string/permlab_readMediaVideo" - android:description="@string/permdesc_readMediaVideo" - android:protectionLevel="dangerous" /> - - <!-- Allows an application to read image files from external storage. - <p>This permission is enforced starting in API level - {@link android.os.Build.VERSION_CODES#TIRAMISU}. - For apps with a <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code - targetSdkVersion}</a> of {@link android.os.Build.VERSION_CODES#S} or lower, this permission - must not be used and the READ_EXTERNAL_STORAGE permission must be used instead. - <p>Protection level: dangerous --> - <permission android:name="android.permission.READ_MEDIA_IMAGE" - android:permissionGroup="android.permission-group.UNDEFINED" - android:label="@string/permlab_readMediaImage" - android:description="@string/permdesc_readMediaImage" - android:protectionLevel="dangerous" /> - <!-- Allows an application to write to external storage. <p class="note"><strong>Note:</strong> If <em>both</em> your <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code @@ -1144,6 +1090,15 @@ android:description="@string/permdesc_accessBackgroundLocation" android:protectionLevel="dangerous|instant" /> + <!-- Allows an application (emergency or advanced driver-assistance app) to bypass + location settings. + <p>Not for use by third-party applications. + @SystemApi + @hide + --> + <permission android:name="android.permission.LOCATION_BYPASS" + android:protectionLevel="signature|privileged"/> + <!-- ====================================================================== --> <!-- Permissions for accessing the call log --> <!-- ====================================================================== --> @@ -1647,7 +1602,7 @@ android:protectionLevel="normal" android:permissionFlags="removed"/> - <!-- @hide We need to keep this around for backwards compatibility --> + <!-- @SystemApi @hide We need to keep this around for backwards compatibility --> <permission android:name="android.permission.WRITE_SMS" android:protectionLevel="normal" android:permissionFlags="removed"/> @@ -1725,7 +1680,7 @@ <permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST" android:protectionLevel="signature|privileged" /> - <!-- Allows an application to monitor incoming Bluetooth MAP messages, to record + <!-- @SystemApi Allows an application to monitor incoming Bluetooth MAP messages, to record or perform processing on them. --> <!-- @hide --> <permission android:name="android.permission.RECEIVE_BLUETOOTH_MAP" @@ -4672,6 +4627,13 @@ <permission android:name="android.permission.READ_FRAME_BUFFER" android:protectionLevel="signature|recents" /> + <!-- @SystemApi Allows an application to change the touch mode state. + Without this permission, an app can only change the touch mode + if it currently has focus. + @hide --> + <permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE" + android:protectionLevel="signature" /> + <!-- Allows an application to use InputFlinger's low level features. @hide --> <permission android:name="android.permission.ACCESS_INPUT_FLINGER" @@ -6164,6 +6126,12 @@ <permission android:name="android.permission.ACCESS_FPS_COUNTER" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows the GameService provider to create GameSession and call GameSession + APIs and overlay a view on top of the game's Activity. + @hide --> + <permission android:name="android.permission.MANAGE_GAME_ACTIVITY" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager when they are performing reboot-blocking work. @hide --> @@ -6290,6 +6258,15 @@ <permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE" android:protectionLevel="signature" /> + <!-- @SystemApi Allows an app to set keep-clear areas without restrictions on the size or + number of keep-clear areas (see {@link android.view.View#setPreferKeepClearRects}). + When the system arranges floating windows onscreen, it might decide to ignore keep-clear + areas from windows, whose owner does not have this permission. + @hide + --> + <permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" + android:protectionLevel="signature|privileged" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> @@ -6542,6 +6519,14 @@ android:exported="false"> </activity> + <activity android:name="com.android.server.logcat.LogAccessConfirmationActivity" + android:theme="@style/Theme.Dialog.Confirmation" + android:excludeFromRecents="true" + android:process=":ui" + android:label="@string/log_access_confirmation_title" + android:exported="false"> + </activity> + <activity android:name="com.android.server.notification.NASLearnMoreActivity" android:theme="@style/Theme.Dialog.Confirmation" android:excludeFromRecents="true" diff --git a/core/res/res/drawable-car/car_checkbox.xml b/core/res/res/drawable-car/car_checkbox.xml deleted file mode 100644 index 083a7aa193aa..000000000000 --- a/core/res/res/drawable-car/car_checkbox.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2019 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:width="@*android:dimen/car_primary_icon_size" - android:height="@*android:dimen/car_primary_icon_size" - android:drawable="@drawable/btn_check_material_anim"/> - <item - android:width="@*android:dimen/car_primary_icon_size" - android:height="@*android:dimen/car_primary_icon_size" - android:drawable="@drawable/car_checkbox_background"/> -</layer-list> diff --git a/core/res/res/drawable-car/car_checkbox_background.xml b/core/res/res/drawable-car/car_checkbox_background.xml deleted file mode 100644 index 69dcdbb0e94c..000000000000 --- a/core/res/res/drawable-car/car_checkbox_background.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright 2021 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_focused="true" android:state_pressed="true"> - <shape android:shape="rectangle"> - <solid android:color="#8A0041BE" /> - <stroke android:width="4dp" android:color="#0041BE" /> - </shape> - </item> - <item android:state_focused="true"> - <shape android:shape="rectangle"> - <solid android:color="#3D0059B3" /> - <stroke android:width="8dp" android:color="#0059B3" /> - </shape> - </item> -</selector> diff --git a/core/res/res/drawable/perm_group_read_media_aural.xml b/core/res/res/drawable/perm_group_read_media_aural.xml deleted file mode 100644 index 6fc9c69254cc..000000000000 --- a/core/res/res/drawable/perm_group_read_media_aural.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> - <path - android:fillColor="@android:color/white" - android:pathData="M10,21q-1.65,0 -2.825,-1.175Q6,18.65 6,17q0,-1.65 1.175,-2.825Q8.35,13 10,13q0.575,0 1.063,0.137 0.487,0.138 0.937,0.413V3h6v4h-4v10q0,1.65 -1.175,2.825Q11.65,21 10,21z"/> -</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/perm_group_read_media_visual.xml b/core/res/res/drawable/perm_group_read_media_visual.xml deleted file mode 100644 index a5db2718c983..000000000000 --- a/core/res/res/drawable/perm_group_read_media_visual.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - 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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> - <path - android:fillColor="@android:color/white" - android:pathData="M9,14h10l-3.45,-4.5 -2.3,3 -1.55,-2zM8,18q-0.825,0 -1.412,-0.587Q6,16.825 6,16L6,4q0,-0.825 0.588,-1.413Q7.175,2 8,2h12q0.825,0 1.413,0.587Q22,3.175 22,4v12q0,0.825 -0.587,1.413Q20.825,18 20,18zM8,16h12L20,4L8,4v12zM4,22q-0.825,0 -1.413,-0.587Q2,20.825 2,20L2,6h2v14h14v2zM8,4v12L8,4z"/> -</vector>
\ No newline at end of file diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 7916ef43da50..04e29890568a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2885,7 +2885,7 @@ <code>public void sayHello(View v)</code> method of your context (typically, your Activity). {@deprecated View actually traverses the Context - hierarchy looking for the relevant method, which is fragile (an intermediate + hierarchy looking for the relevant method, which is fragile (an intermediate ContextWrapper adding a same-named method would change behavior) and restricts bytecode optimizers such as R8. Instead, use View.setOnClickListener.}--> <attr name="onClick" format="string" /> @@ -6975,6 +6975,34 @@ <attr name="toBottom" format="fraction" /> </declare-styleable> + <!-- Defines the ExtendAnimation used to extend windows during animations --> + <declare-styleable name="ExtendAnimation"> + <!-- Defines the amount a window should be extended outward from the left at + the start of the animation. --> + <attr name="fromExtendLeft" format="float|fraction" /> + <!-- Defines the amount a window should be extended outward from the top at + the start of the animation. --> + <attr name="fromExtendTop" format="float|fraction" /> + <!-- Defines the amount a window should be extended outward from the right at + the start of the animation. --> + <attr name="fromExtendRight" format="float|fraction" /> + <!-- Defines the amount a window should be extended outward from the bottom at + the start of the animation. --> + <attr name="fromExtendBottom" format="float|fraction" /> + <!-- Defines the amount a window should be extended outward from the left by + the end of the animation by transitioning from the fromExtendLeft amount. --> + <attr name="toExtendLeft" format="float|fraction" /> + <!-- Defines the amount a window should be extended outward from the top by + the end of the animation by transitioning from the fromExtendTop amount. --> + <attr name="toExtendTop" format="float|fraction" /> + <!-- Defines the amount a window should be extended outward from the right by + the end of the animation by transitioning from the fromExtendRight amount. --> + <attr name="toExtendRight" format="float|fraction" /> + <!-- Defines the amount a window should be extended outward from the bottom by + the end of the animation by transitioning from the fromExtendBottom amount. --> + <attr name="toExtendBottom" format="float|fraction" /> + </declare-styleable> + <declare-styleable name="LayoutAnimation"> <!-- Fraction of the animation duration used to delay the beginning of the animation of each child. --> @@ -7827,7 +7855,7 @@ <!-- Name of a method on the Context used to inflate the menu that will be called when the item is clicked. - {@deprecated Menu actually traverses the Context hierarchy looking for the + {@deprecated Menu actually traverses the Context hierarchy looking for the relevant method, which is fragile (an intermediate ContextWrapper adding a same-named method would change behavior) and restricts bytecode optimizers such as R8. Instead, use MenuItem.setOnMenuItemClickListener.} --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 78841fc914c4..a06b2cb28037 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2612,6 +2612,10 @@ will be locked. --> <bool name="config_multiuserDelayUserDataLocking">false</bool> + <!-- Whether to automatically switch a non-primary user back to the primary user after a + timeout when the device is docked. --> + <bool name="config_enableTimeoutToUserZeroWhenDocked">false</bool> + <!-- Whether to only install system packages on a user if they're allowlisted for that user type. These are flags and can be freely combined. 0 - disable allowlist (install all system packages; no logging) @@ -2707,6 +2711,9 @@ Values are bandwidth_estimator, carrier_config and modem. --> <string name="config_bandwidthEstimateSource">bandwidth_estimator</string> + <!-- Whether force to enable telephony new data stack or not --> + <bool name="config_force_enable_telephony_new_data_stack">false</bool> + <!-- Whether WiFi display is supported by this device. There are many prerequisites for this feature to work correctly. Here are a few of them: @@ -2884,6 +2891,11 @@ <string name="config_sensorUseStartedActivity" translatable="false" >com.android.systemui/com.android.systemui.sensorprivacy.SensorUseStartedActivity</string> + <!-- Component name of the activity used to ask a user to confirm system language change after + receiving <Set Menu Language> CEC message. --> + <string name="config_hdmiCecSetMenuLanguageActivity" + >com.android.systemui/com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity</string> + <!-- Name of the dialog that is used to request the user's consent for a Platform VPN --> <string name="config_platformVpnConfirmDialogComponent" translatable="false" >com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog</string> @@ -4971,6 +4983,10 @@ <!-- URI used for Nearby Share SliceProvider scanning. --> <string translatable="false" name="config_defaultNearbySharingSliceUri"></string> + <!-- Component name that accepts settings intents for saved devices. + Used by FastPairSettingsFragment. --> + <string translatable="false" name="config_defaultNearbyFastPairSettingsDevicesComponent"></string> + <!-- Boolean indicating whether frameworks needs to reset cell broadcast geo-fencing check after reboot or airplane mode toggling --> <bool translatable="false" name="reset_geo_fencing_check_after_boot_or_apm">false</bool> @@ -5133,6 +5149,9 @@ If given value is outside of this range, the option 1 (center) is assummed. --> <integer name="config_letterboxDefaultPositionForReachability">1</integer> + <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. --> + <bool name="config_letterboxIsEducationEnabled">false</bool> + <!-- Whether a camera compat controller is enabled to allow the user to apply or revert treatment for stretched issues in camera viewfinder. --> <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool> @@ -5634,4 +5653,13 @@ <!-- The amount of time after becoming non-interactive (in ms) after which Low Power Standby can activate. --> <integer name="config_lowPowerStandbyNonInteractiveTimeout">5000</integer> + + + <!-- Mapping to select an Intent.EXTRA_DOCK_STATE value from extcon state + key-value pairs. Each entry is evaluated in order and is of the form: + "[EXTRA_DOCK_STATE value],key1=value1,key2=value2[,...]" + An entry with no key-value pairs is valid and can be used as a wildcard. + --> + <string-array name="config_dockExtconStateMapping"> + </string-array> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 2e96c65ef17f..047c04b3596c 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3260,6 +3260,14 @@ <public name="inheritKeyStoreKeys" /> <public name="preferKeepClear" /> <public name="autoHandwritingEnabled" /> + <public name="fromExtendLeft" /> + <public name="fromExtendTop" /> + <public name="fromExtendRight" /> + <public name="fromExtendBottom" /> + <public name="toExtendLeft" /> + <public name="toExtendTop" /> + <public name="toExtendRight" /> + <public name="toExtendBottom" /> </staging-public-group> <staging-public-group type="id" first-id="0x01de0000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 49a12d12ed21..2be685c1429e 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -880,16 +880,6 @@ <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgroupdesc_storage">access photos, media, and files on your device</string> - <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]--> - <string name="permgrouplab_readMediaAural">Music & other audio</string> - <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]--> - <string name="permgroupdesc_readMediaAural">access audio files on your device</string> - - <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=40]--> - <string name="permgrouplab_readMediaVisual">Photos & videos</string> - <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]--> - <string name="permgroupdesc_readMediaVisual">access images and video files on your device</string> - <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgrouplab_microphone">Microphone</string> <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> @@ -1903,21 +1893,6 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> <string name="permdesc_sdcardRead">Allows the app to read the contents of your shared storage.</string> - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> - <string name="permlab_readMediaAudio">read audio files from shared storage</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> - <string name="permdesc_readMediaAudio">Allows the app to read audio files from your shared storage.</string> - - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> - <string name="permlab_readMediaVideo">read video files from shared storage</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> - <string name="permdesc_readMediaVideo">Allows the app to read video files from your shared storage.</string> - - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> - <string name="permlab_readMediaImage">read image files from shared storage</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> - <string name="permdesc_readMediaImage">Allows the app to read image files from your shared storage.</string> - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] --> <string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] --> @@ -5714,6 +5689,20 @@ <!-- Title for the harmful app warning dialog. [CHAR LIMIT=40] --> <string name="harmful_app_warning_title">Harmful app detected</string> + <!-- Title for the log access confirmation dialog. [CHAR LIMIT=40] --> + <string name="log_access_confirmation_title">System log access request</string> + <!-- Label for the allow button on the log access confirmation dialog. [CHAR LIMIT=20] --> + <string name="log_access_confirmation_allow">Only this time</string> + <!-- Label for the deny button on the log access confirmation dialog. [CHAR LIMIT=20] --> + <string name="log_access_confirmation_deny">Don\u2019t allow</string> + + <!-- Content for the log access confirmation dialog. [CHAR LIMIT=NONE]--> + <string name="log_access_confirmation_body"><xliff:g id="log_access_app_name" example="Example App">%s</xliff:g> requests system logs for functional debugging. + These logs might contain information that apps and services on your device have written.</string> + + <!-- Privacy notice do not show [CHAR LIMIT=20] --> + <string name="log_access_do_not_show_again">Don\u2019t show again</string> + <!-- Text describing a permission request for one app to show another app's slices [CHAR LIMIT=NONE] --> <string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 20c5c610abe4..82774c1eec4f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -375,6 +375,7 @@ <java-symbol type="string" name="config_usbConfirmActivity" /> <java-symbol type="string" name="config_usbResolverActivity" /> <java-symbol type="string" name="config_sensorUseStartedActivity" /> + <java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" /> <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" /> <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" /> <java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" /> @@ -465,6 +466,7 @@ <java-symbol type="integer" name="config_multiuserMaximumUsers" /> <java-symbol type="integer" name="config_multiuserMaxRunningUsers" /> <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" /> + <java-symbol type="bool" name="config_enableTimeoutToUserZeroWhenDocked" /> <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/> <java-symbol type="xml" name="config_user_types" /> <java-symbol type="integer" name="config_safe_media_volume_index" /> @@ -472,6 +474,7 @@ <java-symbol type="integer" name="config_mobile_mtu" /> <java-symbol type="array" name="config_mobile_tcp_buffers" /> <java-symbol type="string" name="config_tcp_buffers" /> + <java-symbol type="bool" name="config_force_enable_telephony_new_data_stack" /> <java-symbol type="integer" name="config_volte_replacement_rat"/> <java-symbol type="integer" name="config_valid_wappush_index" /> <java-symbol type="integer" name="config_overrideHasPermanentMenuKey" /> @@ -3845,6 +3848,11 @@ <java-symbol type="string" name="harmful_app_warning_title" /> <java-symbol type="layout" name="harmful_app_warning_dialog" /> + <java-symbol type="string" name="log_access_confirmation_allow" /> + <java-symbol type="string" name="log_access_confirmation_deny" /> + <java-symbol type="string" name="log_access_confirmation_title" /> + <java-symbol type="string" name="log_access_confirmation_body" /> + <java-symbol type="string" name="config_defaultAssistantAccessComponent" /> <java-symbol type="string" name="slices_permission_request" /> @@ -4098,6 +4106,7 @@ <java-symbol type="layout" name="chooser_action_button" /> <java-symbol type="dimen" name="chooser_action_button_icon_size" /> <java-symbol type="string" name="config_defaultNearbySharingComponent" /> + <java-symbol type="string" name="config_defaultNearbyFastPairSettingsDevicesComponent" /> <java-symbol type="bool" name="config_disable_all_cb_messages" /> <java-symbol type="drawable" name="ic_close" /> @@ -4328,6 +4337,7 @@ <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" /> <java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" /> <java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" /> + <java-symbol type="bool" name="config_letterboxIsEducationEnabled" /> <java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" /> <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" /> @@ -4675,6 +4685,8 @@ <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" /> + <java-symbol type="array" name="config_dockExtconStateMapping" /> + <java-symbol type="string" name="notification_channel_abusive_bg_apps"/> <java-symbol type="string" name="notification_title_abusive_bg_apps"/> <java-symbol type="string" name="notification_content_abusive_bg_apps"/> diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout.xml b/core/tests/coretests/res/layout/remote_view_relative_layout.xml new file mode 100644 index 000000000000..713a4c89ea4d --- /dev/null +++ b/core/tests/coretests/res/layout/remote_view_relative_layout.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 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. +*/ +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml new file mode 100644 index 000000000000..74c939b7eaa3 --- /dev/null +++ b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 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. +*/ +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/themed_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:theme="@style/RelativeLayoutAlignTop25Alpha"/> diff --git a/core/tests/coretests/res/layout/remote_views_light_background_text.xml b/core/tests/coretests/res/layout/remote_views_light_background_text.xml new file mode 100644 index 000000000000..f300f0991a97 --- /dev/null +++ b/core/tests/coretests/res/layout/remote_views_light_background_text.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2016 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/light_background_text" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/core/tests/coretests/res/layout/remote_views_list.xml b/core/tests/coretests/res/layout/remote_views_list.xml new file mode 100644 index 000000000000..ca43bc8986e7 --- /dev/null +++ b/core/tests/coretests/res/layout/remote_views_list.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2016 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<ListView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml index 352b4dceb3cc..32eebb35e0c3 100644 --- a/core/tests/coretests/res/values/styles.xml +++ b/core/tests/coretests/res/values/styles.xml @@ -34,6 +34,16 @@ <style name="LayoutInDisplayCutoutModeAlways"> <item name="android:windowLayoutInDisplayCutoutMode">always</item> </style> + <style name="RelativeLayoutAlignBottom50Alpha"> + <item name="android:layout_alignParentTop">false</item> + <item name="android:layout_alignParentBottom">true</item> + <item name="android:alpha">0.5</item> + </style> + <style name="RelativeLayoutAlignTop25Alpha"> + <item name="android:layout_alignParentTop">true</item> + <item name="android:layout_alignParentBottom">false</item> + <item name="android:alpha">0.25</item> + </style> <style name="WindowBackgroundColorLiteral"> <item name="android:windowBackground">#00FF00</item> </style> diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java index d3e8bb0ec317..5338d046596a 100644 --- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java @@ -37,9 +37,9 @@ import org.junit.Test; @SmallTest public class PropertyInvalidatedCacheTests { - // This property is never set. The test process does not have permission to set any - // properties. - static final String CACHE_PROPERTY = "cache_key.cache_test_a"; + // Configuration for creating caches + private static final int MODULE = PropertyInvalidatedCache.MODULE_TEST; + private static final String API = "testApi"; // This class is a proxy for binder calls. It contains a counter that increments // every time the class is queried. @@ -64,6 +64,25 @@ public class PropertyInvalidatedCacheTests { } } + // The functions for querying the server. + private static class ServerQuery + extends PropertyInvalidatedCache.QueryHandler<Integer, Boolean> { + private final ServerProxy mServer; + + ServerQuery(ServerProxy server) { + mServer = server; + } + + @Override + public Boolean apply(Integer x) { + return mServer.query(x); + } + @Override + public boolean shouldBypassCache(Integer x) { + return x % 13 == 0; + } + } + // Clear the test mode after every test, in case this process is used for other // tests. This also resets the test property map. @After @@ -82,19 +101,11 @@ public class PropertyInvalidatedCacheTests { // Create a cache that uses simple arithmetic to computer its values. PropertyInvalidatedCache<Integer, Boolean> testCache = - new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { - @Override - public Boolean recompute(Integer x) { - return tester.query(x); - } - @Override - public boolean bypass(Integer x) { - return x % 13 == 0; - } - }; + new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", + new ServerQuery(tester)); PropertyInvalidatedCache.setTestMode(true); - PropertyInvalidatedCache.testPropertyName(CACHE_PROPERTY); + testCache.testPropertyName(); tester.verify(0); assertEquals(tester.value(3), testCache.query(3)); @@ -136,26 +147,14 @@ public class PropertyInvalidatedCacheTests { // Three caches, all using the same system property but one uses a different name. PropertyInvalidatedCache<Integer, Boolean> cache1 = - new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { - @Override - public Boolean recompute(Integer x) { - return tester.query(x); - } - }; + new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", + new ServerQuery(tester)); PropertyInvalidatedCache<Integer, Boolean> cache2 = - new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { - @Override - public Boolean recompute(Integer x) { - return tester.query(x); - } - }; + new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", + new ServerQuery(tester)); PropertyInvalidatedCache<Integer, Boolean> cache3 = - new PropertyInvalidatedCache<>(4, CACHE_PROPERTY, "cache3") { - @Override - public Boolean recompute(Integer x) { - return tester.query(x); - } - }; + new PropertyInvalidatedCache<>(4, MODULE, API, "cache3", + new ServerQuery(tester)); // Caches are enabled upon creation. assertEquals(false, cache1.getDisabledState()); @@ -176,61 +175,70 @@ public class PropertyInvalidatedCacheTests { assertEquals(false, cache3.getDisabledState()); // Create a new cache1. Verify that the new instance is disabled. - cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { - @Override - public Boolean recompute(Integer x) { - return tester.query(x); - } - }; + cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", + new ServerQuery(tester)); assertEquals(true, cache1.getDisabledState()); // Remove the record of caches being locally disabled. This is a clean-up step. - cache1.clearDisableLocal(); + cache1.forgetDisableLocal(); assertEquals(true, cache1.getDisabledState()); assertEquals(true, cache2.getDisabledState()); assertEquals(false, cache3.getDisabledState()); // Create a new cache1. Verify that the new instance is not disabled. - cache1 = new PropertyInvalidatedCache<>(4, CACHE_PROPERTY) { - @Override - public Boolean recompute(Integer x) { - return tester.query(x); - } - }; + cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", + new ServerQuery(tester)); assertEquals(false, cache1.getDisabledState()); } - private static final String UNSET_KEY = "Aiw7woh6ie4toh7W"; + private static class TestQuery + extends PropertyInvalidatedCache.QueryHandler<Integer, String> { + + private int mRecomputeCount = 0; + + @Override + public String apply(Integer qv) { + mRecomputeCount += 1; + return "foo" + qv.toString(); + } + + int getRecomputeCount() { + return mRecomputeCount; + } + } private static class TestCache extends PropertyInvalidatedCache<Integer, String> { + private final TestQuery mQuery; + TestCache() { - this(CACHE_PROPERTY); + this(MODULE, API); } - TestCache(String key) { - super(4, key); + TestCache(int module, String api) { + this(module, api, new TestQuery()); setTestMode(true); - testPropertyName(key); + testPropertyName(); } - @Override - public String recompute(Integer qv) { - mRecomputeCount += 1; - return "foo" + qv.toString(); + TestCache(int module, String api, TestQuery query) { + super(4, module, api, api, query); + mQuery = query; + setTestMode(true); + testPropertyName(); } - int getRecomputeCount() { - return mRecomputeCount; + public int getRecomputeCount() { + return mQuery.getRecomputeCount(); } - private int mRecomputeCount = 0; + } @Test public void testCacheRecompute() { TestCache cache = new TestCache(); cache.invalidateCache(); - assertEquals(cache.isDisabledLocal(), false); + assertEquals(cache.isDisabled(), false); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); @@ -241,6 +249,11 @@ public class PropertyInvalidatedCacheTests { assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); + // Invalidate the cache with a direct call to the property. + PropertyInvalidatedCache.invalidateCache(MODULE, API); + assertEquals("foo5", cache.query(5)); + assertEquals("foo5", cache.query(5)); + assertEquals(4, cache.getRecomputeCount()); } @Test @@ -257,7 +270,8 @@ public class PropertyInvalidatedCacheTests { @Test public void testCachePropertyUnset() { - TestCache cache = new TestCache(UNSET_KEY); + final String UNSET_API = "otherApi"; + TestCache cache = new TestCache(MODULE, UNSET_API); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(2, cache.getRecomputeCount()); @@ -327,17 +341,40 @@ public class PropertyInvalidatedCacheTests { @Test public void testLocalProcessDisable() { TestCache cache = new TestCache(); - assertEquals(cache.isDisabledLocal(), false); + assertEquals(cache.isDisabled(), false); cache.invalidateCache(); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); assertEquals("foo5", cache.query(5)); assertEquals(1, cache.getRecomputeCount()); - assertEquals(cache.isDisabledLocal(), false); + assertEquals(cache.isDisabled(), false); cache.disableLocal(); - assertEquals(cache.isDisabledLocal(), true); + assertEquals(cache.isDisabled(), true); assertEquals("foo5", cache.query(5)); assertEquals("foo5", cache.query(5)); assertEquals(3, cache.getRecomputeCount()); } + + @Test + public void testPropertyNames() { + String n1; + n1 = PropertyInvalidatedCache.createPropertyName( + PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo"); + assertEquals(n1, "cache_key.system_server.get_package_info"); + n1 = PropertyInvalidatedCache.createPropertyName( + PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info"); + assertEquals(n1, "cache_key.system_server.get_package_info"); + try { + n1 = PropertyInvalidatedCache.createPropertyName( + PropertyInvalidatedCache.MODULE_SYSTEM - 1, "get package_info"); + // n1 is an invalid api name. + assertEquals(false, true); + } catch (IllegalArgumentException e) { + // An exception is expected here. + } + + n1 = PropertyInvalidatedCache.createPropertyName( + PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState"); + assertEquals(n1, "cache_key.bluetooth.get_state"); + } } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index b2c427408d01..5c9044c56f95 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -646,6 +646,10 @@ public class TransactionParcelTests { } @Override + public void dumpResources(ParcelFileDescriptor fd, RemoteCallback finishCallback) { + } + + @Override public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) { } diff --git a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java index b66642c20808..fa657f7a8928 100644 --- a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java +++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java @@ -71,6 +71,8 @@ public class CrossProfileAppsTest { private Resources mResources; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Drawable mDrawable; + @Mock + private PackageManager mPackageManager; private CrossProfileApps mCrossProfileApps; @Before @@ -87,6 +89,7 @@ public class CrossProfileAppsTest { Context.DEVICE_POLICY_SERVICE); when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( mDevicePolicyManager); + when(mContext.getPackageManager()).thenReturn(mPackageManager); } @Before @@ -131,7 +134,8 @@ public class CrossProfileAppsTest { setValidTargetProfile(MANAGED_PROFILE); mCrossProfileApps.getProfileSwitchingIconDrawable(MANAGED_PROFILE); - verify(mResources).getDrawable(R.drawable.ic_corp_badge, null); + verify(mPackageManager).getUserBadgeForDensityNoBackground( + MANAGED_PROFILE, /* density= */0); } @Test diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java index 947da0b07234..0629a999e6bf 100644 --- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java +++ b/core/tests/coretests/src/android/content/pm/InstallLocationUtilsTests.java @@ -25,7 +25,7 @@ import android.platform.test.annotations.Presubmit; import android.test.AndroidTestCase; import android.util.Log; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import org.mockito.Mockito; @@ -36,10 +36,9 @@ import java.util.List; import java.util.UUID; @Presubmit -public class PackageHelperTests extends AndroidTestCase { +public class InstallLocationUtilsTests extends AndroidTestCase { private static final boolean localLOGV = true; public static final String TAG = "PackageHelperTests"; - protected final String PREFIX = "android.content.pm"; private static final String sInternalVolPath = "/data"; private static final String sAdoptedVolPath = "/mnt/expand/123"; @@ -88,11 +87,14 @@ public class PackageHelperTests extends AndroidTestCase { UUID internalUuid = UUID.randomUUID(); UUID adoptedUuid = UUID.randomUUID(); UUID publicUuid = UUID.randomUUID(); - Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize); + Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)) + .thenReturn(sInternalSize); Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize); Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize); - Mockito.when(storageManager.getUuidForPath(Mockito.eq(internalFile))).thenReturn(internalUuid); - Mockito.when(storageManager.getUuidForPath(Mockito.eq(adoptedFile))).thenReturn(adoptedUuid); + Mockito.when(storageManager.getUuidForPath(Mockito.eq(internalFile))) + .thenReturn(internalUuid); + Mockito.when(storageManager.getUuidForPath(Mockito.eq(adoptedFile))) + .thenReturn(adoptedUuid); Mockito.when(storageManager.getUuidForPath(Mockito.eq(publicFile))).thenReturn(publicUuid); Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(internalUuid), Mockito.anyInt())) .thenReturn(sInternalSize); @@ -103,7 +105,7 @@ public class PackageHelperTests extends AndroidTestCase { return storageManager; } - private static final class MockedInterface extends PackageHelper.TestableInterface { + private static final class MockedInterface extends InstallLocationUtils.TestableInterface { private boolean mForceAllowOnExternal = false; private boolean mAllow3rdPartyOnInternal = true; private ApplicationInfo mApplicationInfo = null; @@ -164,25 +166,25 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, true /*allow 3rd party on internal*/); String volume = null; - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, true /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, false /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, false /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location*/, 1000 /*size bytes*/, mockedInterface); assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume); @@ -192,7 +194,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, true /*allow 3rd party on internal*/); try { - PackageHelper.resolveInstallVolume(getContext(), "package.name", + InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); fail("Expected exception in resolveInstallVolume was not thrown"); } catch(IOException e) { @@ -202,7 +204,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, true /*allow 3rd party on internal*/); try { - PackageHelper.resolveInstallVolume(getContext(), "package.name", + InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); fail("Expected exception in resolveInstallVolume was not thrown"); } catch(IOException e) { @@ -212,7 +214,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/, false /*allow 3rd party on internal*/); try { - PackageHelper.resolveInstallVolume(getContext(), "package.name", + InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); fail("Expected exception in resolveInstallVolume was not thrown"); } catch(IOException e) { @@ -222,7 +224,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/, false /*allow 3rd party on internal*/); try { - PackageHelper.resolveInstallVolume(getContext(), "package.name", + InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location*/, 1000000 /*size bytes*/, mockedInterface); fail("Expected exception in resolveInstallVolume was not thrown"); } catch(IOException e) { @@ -240,13 +242,13 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, false /*force allow on external*/, true /*allow 3rd party on internal*/); String volume = null; - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sInternalVolUuid, volume); mockedInterface.setMockValues(appInfo, true /*force allow on external*/, true /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sInternalVolUuid, volume); } @@ -260,25 +262,25 @@ public class PackageHelperTests extends AndroidTestCase { appInfo.volumeUuid = sAdoptedVolUuid; mockedInterface.setMockValues(appInfo, false /*force allow on external*/, true /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sAdoptedVolUuid, volume); mockedInterface.setMockValues(appInfo, true /*force allow on external*/, true /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sAdoptedVolUuid, volume); mockedInterface.setMockValues(appInfo, false /*force allow on external*/, false /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sAdoptedVolUuid, volume); mockedInterface.setMockValues(appInfo, true /*force allow on external*/, false /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sAdoptedVolUuid, volume); } @@ -292,7 +294,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, false /*force allow on external*/, true /*allow 3rd party on internal*/); try { - PackageHelper.resolveInstallVolume(getContext(), "package.name", + InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); fail("Expected exception was not thrown " + appInfo.volumeUuid); } catch (IOException e) { @@ -302,7 +304,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, false /*force allow on external*/, false /*allow 3rd party on internal*/); try { - PackageHelper.resolveInstallVolume(getContext(), "package.name", + InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); fail("Expected exception was not thrown " + appInfo.volumeUuid); } catch (IOException e) { @@ -312,7 +314,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, true /*force allow on external*/, false /*allow 3rd party on internal*/); try { - PackageHelper.resolveInstallVolume(getContext(), "package.name", + InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); fail("Expected exception was not thrown " + appInfo.volumeUuid); } catch (IOException e) { @@ -322,7 +324,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, true /*force allow on external*/, true /*allow 3rd party on internal*/); try { - PackageHelper.resolveInstallVolume(getContext(), "package.name", + InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface); fail("Expected exception was not thrown " + appInfo.volumeUuid); } catch (IOException e) { @@ -336,28 +338,28 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, false /*force allow on external*/, true /*allow 3rd party on internal*/); String volume = null; - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); // Should return the volume with bigger available space. assertEquals(sInternalVolUuid, volume); mockedInterface.setMockValues(appInfo, true /*force allow on external*/, true /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); // Should return the volume with bigger available space. assertEquals(sInternalVolUuid, volume); mockedInterface.setMockValues(appInfo, true /*force allow on external*/, false /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); // Should return the volume with bigger available space. assertEquals(sAdoptedVolUuid, volume); mockedInterface.setMockValues(appInfo, false /*force allow on external*/, false /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); // Should return the volume with bigger available space. assertEquals(sAdoptedVolUuid, volume); @@ -371,20 +373,20 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, false /*force allow on external*/, true /*allow 3rd party on internal*/); String volume = null; - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sInternalVolUuid, volume); mockedInterface.setMockValues(appInfo, true /*force allow on external*/, true /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sInternalVolUuid, volume); mockedInterface.setMockValues(appInfo, false /*force allow on external*/, false /*allow 3rd party on internal*/); try { - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); fail("Expected exception in resolveInstallVolume was not thrown"); } catch (IOException e) { @@ -395,7 +397,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, true /*force allow on external*/, false /*allow 3rd party on internal*/); volume = null; - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sAdoptedVolUuid, volume); } @@ -407,7 +409,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, false /*force allow on external*/, false /*allow 3rd party on internal*/); String volume = null; - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); // Should return the non-internal volume. assertEquals(sAdoptedVolUuid, volume); @@ -415,7 +417,7 @@ public class PackageHelperTests extends AndroidTestCase { appInfo = null; mockedInterface.setMockValues(appInfo, true /*force allow on external*/, false /*allow 3rd party on internal*/); - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface); // Should return the non-internal volume. assertEquals(sAdoptedVolUuid, volume); @@ -428,7 +430,7 @@ public class PackageHelperTests extends AndroidTestCase { true /*allow 3rd party on internal*/); String volume = null; try { - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location internal ONLY*/, 1000000 /*size too big*/, mockedInterface); fail("Expected exception in resolveInstallVolume was not thrown"); @@ -445,7 +447,7 @@ public class PackageHelperTests extends AndroidTestCase { false /*allow 3rd party on internal*/); String volume = null; try { - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); fail("Expected exception in resolveInstallVolume was not thrown"); } catch (IOException e) { @@ -456,7 +458,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, true /*force allow on external*/, false /*allow 3rd party on internal*/); volume = null; - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sAdoptedVolUuid, volume); @@ -474,7 +476,7 @@ public class PackageHelperTests extends AndroidTestCase { mockedInterface.setMockValues(appInfo, true /*force allow on external*/, false /*allow 3rd party on internal*/); String volume = null; - volume = PackageHelper.resolveInstallVolume(getContext(), "package.name", + volume = InstallLocationUtils.resolveInstallVolume(getContext(), "package.name", 1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface); assertEquals(sAdoptedVolUuid, volume); } diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java index 104f077e5ad2..f7ca822c36e2 100644 --- a/core/tests/coretests/src/android/os/VibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java @@ -146,6 +146,7 @@ public class VibrationEffectTest { @Test public void testValidateWaveformBuilder() { + // Cover builder methods VibrationEffect.startWaveform(targetAmplitude(1)) .addTransition(Duration.ofSeconds(1), targetAmplitude(0.5f), targetFrequency(100)) .addTransition(Duration.ZERO, targetAmplitude(0f), targetFrequency(200)) @@ -158,6 +159,39 @@ public class VibrationEffectTest { .build() .validate(); + // Make sure class summary javadoc examples compile and are valid. + // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE WaveformBuilder javadocs. + VibrationEffect.startWaveform(targetFrequency(60)) + .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) + .addSustain(Duration.ofMillis(200)) + .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) + .build() + .validate(); + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) + .addOffDuration(Duration.ofMillis(20)) + .repeatEffectIndefinitely( + VibrationEffect.startWaveform(targetAmplitude(0.2f)) + .addSustain(Duration.ofMillis(10)) + .addTransition(Duration.ofMillis(20), targetAmplitude(0.4f)) + .addSustain(Duration.ofMillis(30)) + .addTransition(Duration.ofMillis(40), targetAmplitude(0.8f)) + .addSustain(Duration.ofMillis(50)) + .addTransition(Duration.ofMillis(60), targetAmplitude(0.2f)) + .build()) + .compose() + .validate(); + VibrationEffect.createWaveform(new long[]{10, 20, 30}, new int[]{51, 102, 204}, -1) + .validate(); + VibrationEffect.startWaveform(targetAmplitude(0.2f)) + .addSustain(Duration.ofMillis(10)) + .addTransition(Duration.ZERO, targetAmplitude(0.4f)) + .addSustain(Duration.ofMillis(20)) + .addTransition(Duration.ZERO, targetAmplitude(0.8f)) + .addSustain(Duration.ofMillis(30)) + .build() + .validate(); + assertThrows(IllegalStateException.class, () -> VibrationEffect.startWaveform().build().validate()); assertThrows(IllegalArgumentException.class, () -> targetAmplitude(-2)); @@ -171,6 +205,7 @@ public class VibrationEffectTest { @Test public void testValidateComposed() { + // Cover builder methods VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) .addEffect(TEST_ONE_SHOT) @@ -178,14 +213,31 @@ public class VibrationEffectTest { .addOffDuration(Duration.ofMillis(100)) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0)) .compose() .validate(); - VibrationEffect.startComposition() .repeatEffectIndefinitely(TEST_ONE_SHOT) .compose() .validate(); + // Make sure class summary javadoc examples compile and are valid. + // NOTE: IF THIS IS UPDATED, PLEASE ALSO UPDATE Composition javadocs. + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SLOW_RISE, 0.5f) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, 0.5f) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1.0f, 100) + .compose() + .validate(); + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK) + .addOffDuration(Duration.ofMillis(10)) + .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_DOUBLE_CLICK)) + .addOffDuration(Duration.ofMillis(50)) + .addEffect(VibrationEffect.createWaveform(new long[]{10, 20}, /* repeat= */ 0)) + .compose() + .validate(); + assertThrows(IllegalStateException.class, () -> VibrationEffect.startComposition().compose().validate()); assertThrows(IllegalStateException.class, diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java index 201883689546..99670d98bb84 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java @@ -58,7 +58,7 @@ public class AccessibilityNodeInfoTest { // The number of flags held in boolean properties. Their values should also be double-checked // in the methods above. - private static final int NUM_BOOLEAN_PROPERTIES = 23; + private static final int NUM_BOOLEAN_PROPERTIES = 24; @Test public void testStandardActions_serializationFlagIsValid() { diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 059c764213bc..00b3693c902b 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -16,8 +16,12 @@ package android.widget; +import static com.android.internal.R.id.pending_intent_tag; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -31,7 +35,10 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Binder; +import android.os.Looper; import android.os.Parcel; +import android.util.SizeF; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewGroup; @@ -49,6 +56,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; +import java.util.Map; import java.util.concurrent.CountDownLatch; /** @@ -261,6 +269,148 @@ public class RemoteViewsTest { verifyViewTree(syncView, asyncView, "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2"); } + @Test + public void nestedViews_setRemoteAdapter_intent() { + Looper.prepare(); + + AppWidgetHostView widget = new AppWidgetHostView(mContext); + RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host); + RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host); + RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list); + inner2.setRemoteAdapter(R.id.list, new Intent()); + inner1.addView(R.id.container, inner2); + top.addView(R.id.container, inner1); + + View view = top.apply(mContext, widget); + widget.addView(view); + + ListView listView = (ListView) view.findViewById(R.id.list); + listView.onRemoteAdapterConnected(); + assertNotNull(listView.getAdapter()); + + top.reapply(mContext, view); + listView = (ListView) view.findViewById(R.id.list); + assertNotNull(listView.getAdapter()); + } + + @Test + public void nestedViews_setRemoteAdapter_remoteCollectionItems() { + AppWidgetHostView widget = new AppWidgetHostView(mContext); + RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host); + RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host); + RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list); + inner2.setRemoteAdapter( + R.id.list, + new RemoteViews.RemoteCollectionItems.Builder() + .addItem(0, new RemoteViews(mPackage, R.layout.remote_view_host)) + .build()); + inner1.addView(R.id.container, inner2); + top.addView(R.id.container, inner1); + + View view = top.apply(mContext, widget); + widget.addView(view); + + ListView listView = (ListView) view.findViewById(R.id.list); + assertNotNull(listView.getAdapter()); + + top.reapply(mContext, view); + listView = (ListView) view.findViewById(R.id.list); + assertNotNull(listView.getAdapter()); + } + + @Test + public void nestedViews_collectionChildFlag() throws Exception { + RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text); + nested.setOnClickPendingIntent( + R.id.text, + PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE) + ); + + RemoteViews listItem = new RemoteViews(mPackage, R.layout.remote_view_host); + listItem.addView(R.id.container, nested); + listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD); + + View view = listItem.apply(mContext, mContainer); + TextView text = (TextView) view.findViewById(R.id.text); + assertNull(text.getTag(pending_intent_tag)); + } + + @Test + public void landscapePortraitViews_collectionChildFlag() throws Exception { + RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text); + inner.setOnClickPendingIntent( + R.id.text, + PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE) + ); + + RemoteViews listItem = new RemoteViews(inner, inner); + listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD); + + View view = listItem.apply(mContext, mContainer); + TextView text = (TextView) view.findViewById(R.id.text); + assertNull(text.getTag(pending_intent_tag)); + } + + @Test + public void sizedViews_collectionChildFlag() throws Exception { + RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text); + inner.setOnClickPendingIntent( + R.id.text, + PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE) + ); + + RemoteViews listItem = new RemoteViews( + Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner)); + listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD); + + View view = listItem.apply(mContext, mContainer); + TextView text = (TextView) view.findViewById(R.id.text); + assertNull(text.getTag(pending_intent_tag)); + } + + @Test + public void nestedViews_lightBackgroundLayoutFlag() { + RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text); + nested.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text); + + RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_view_host); + parent.addView(R.id.container, nested); + parent.setLightBackgroundLayoutId(R.layout.remote_view_host); + parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT); + + View view = parent.apply(mContext, mContainer); + assertNull(view.findViewById(R.id.text)); + assertNotNull(view.findViewById(R.id.light_background_text)); + } + + + @Test + public void landscapePortraitViews_lightBackgroundLayoutFlag() { + RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text); + inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text); + + RemoteViews parent = new RemoteViews(inner, inner); + parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT); + + View view = parent.apply(mContext, mContainer); + assertNull(view.findViewById(R.id.text)); + assertNotNull(view.findViewById(R.id.light_background_text)); + } + + @Test + public void sizedViews_lightBackgroundLayoutFlag() { + RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text); + inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text); + + RemoteViews parent = new RemoteViews( + Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner)); + parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT); + + View view = parent.apply(mContext, mContainer); + assertNull(view.findViewById(R.id.text)); + assertNotNull(view.findViewById(R.id.light_background_text)); + } + private RemoteViews createViewChained(int depth, String... texts) { RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host); @@ -483,6 +633,47 @@ public class RemoteViewsTest { index, inflated.getTag(com.android.internal.R.id.notification_action_index_tag)); } + @Test + public void nestedViews_themesPropagateCorrectly() { + Context themedContext = + new ContextThemeWrapper(mContext, R.style.RelativeLayoutAlignBottom50Alpha); + RelativeLayout rootParent = new RelativeLayout(themedContext); + + RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_relative_layout); + RemoteViews inner1 = + new RemoteViews(mPackage, R.layout.remote_view_relative_layout_with_theme); + RemoteViews inner2 = + new RemoteViews(mPackage, R.layout.remote_view_relative_layout); + + inner1.addView(R.id.themed_layout, inner2); + top.addView(R.id.container, inner1); + + RelativeLayout root = (RelativeLayout) top.apply(themedContext, rootParent); + assertEquals(0.5, root.getAlpha(), 0.); + RelativeLayout.LayoutParams rootParams = + (RelativeLayout.LayoutParams) root.getLayoutParams(); + assertEquals(RelativeLayout.TRUE, + rootParams.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM)); + + // The theme is set on inner1View and its descendants. However, inner1View does + // not get its layout params from its theme (though its descendants do), but other + // attributes such as alpha are set. + RelativeLayout inner1View = (RelativeLayout) root.getChildAt(0); + assertEquals(R.id.themed_layout, inner1View.getId()); + assertEquals(0.25, inner1View.getAlpha(), 0.); + RelativeLayout.LayoutParams inner1Params = + (RelativeLayout.LayoutParams) inner1View.getLayoutParams(); + assertEquals(RelativeLayout.TRUE, + inner1Params.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM)); + + RelativeLayout inner2View = (RelativeLayout) inner1View.getChildAt(0); + assertEquals(0.25, inner2View.getAlpha(), 0.); + RelativeLayout.LayoutParams inner2Params = + (RelativeLayout.LayoutParams) inner2View.getLayoutParams(); + assertEquals(RelativeLayout.TRUE, + inner2Params.getRule(RelativeLayout.ALIGN_PARENT_TOP)); + } + private class WidgetContainer extends AppWidgetHostView { int[] mSharedViewIds; String[] mSharedViewNames; 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 b655369d7e60..f5cbffb64bb5 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java @@ -17,6 +17,7 @@ package com.android.internal.os; import static android.os.BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; +import static android.os.BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR; import static android.os.BatteryStats.STATS_SINCE_CHARGED; import static android.os.BatteryStats.WAKE_TYPE_PARTIAL; @@ -30,6 +31,12 @@ import android.os.BatteryStats.Uid.Sensor; import android.os.Process; import android.os.UserHandle; import android.os.WorkSource; +import android.telephony.Annotation; +import android.telephony.CellSignalStrength; +import android.telephony.DataConnectionRealTimeInfo; +import android.telephony.ServiceState; +import android.telephony.TelephonyManager; +import android.util.SparseIntArray; import android.util.SparseLongArray; import android.view.Display; @@ -1165,6 +1172,185 @@ public class BatteryStatsNoteTest extends TestCase { "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi); } + @SmallTest + public void testGetPerStateActiveRadioDurationMs() { + final MockClock clock = new MockClock(); // holds realtime and uptime in ms + final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock); + final int ratCount = BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; + final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1; + final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels(); + + final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount]; + for (int rat = 0; rat < ratCount; rat++) { + for (int freq = 0; freq < frequencyCount; freq++) { + for (int txLvl = 0; txLvl < txLevelCount; txLvl++) { + expectedDurationsMs[rat][freq][txLvl] = 0; + } + } + } + + class ModemAndBatteryState { + public long currentTimeMs = 100; + public boolean onBattery = false; + public boolean modemActive = false; + @Annotation.NetworkType + public int currentNetworkDataType = TelephonyManager.NETWORK_TYPE_UNKNOWN; + @BatteryStats.RadioAccessTechnology + public int currentRat = BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER; + @ServiceState.FrequencyRange + public int currentFrequencyRange = ServiceState.FREQUENCY_RANGE_UNKNOWN; + public SparseIntArray currentSignalStrengths = new SparseIntArray(); + + void setOnBattery(boolean onBattery) { + this.onBattery = onBattery; + bi.updateTimeBasesLocked(onBattery, Display.STATE_OFF, currentTimeMs * 1000, + currentTimeMs * 1000); + } + + void setModemActive(boolean active) { + modemActive = active; + final int state = active ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH + : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW; + bi.noteMobileRadioPowerStateLocked(state, currentTimeMs * 1000_000L, UID); + } + + void setRatType(@Annotation.NetworkType int dataType, + @BatteryStats.RadioAccessTechnology int rat) { + currentNetworkDataType = dataType; + currentRat = rat; + bi.notePhoneDataConnectionStateLocked(dataType, true, ServiceState.STATE_IN_SERVICE, + currentFrequencyRange); + } + + void setFrequencyRange(@ServiceState.FrequencyRange int frequency) { + currentFrequencyRange = frequency; + bi.notePhoneDataConnectionStateLocked(currentNetworkDataType, true, + ServiceState.STATE_IN_SERVICE, frequency); + } + + void setSignalStrength(@BatteryStats.RadioAccessTechnology int rat, int strength) { + currentSignalStrengths.put(rat, strength); + final int size = currentSignalStrengths.size(); + final int newestGenSignalStrength = currentSignalStrengths.valueAt(size - 1); + bi.notePhoneSignalStrengthLocked(newestGenSignalStrength, currentSignalStrengths); + } + } + final ModemAndBatteryState state = new ModemAndBatteryState(); + + IntConsumer incrementTime = inc -> { + state.currentTimeMs += inc; + clock.realtime = clock.uptime = state.currentTimeMs; + + // If the device is not on battery, no timers should increment. + if (!state.onBattery) return; + // If the modem is not active, no timers should increment. + if (!state.modemActive) return; + + final int currentRat = state.currentRat; + final int currentFrequencyRange = + currentRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0; + int currentSignalStrength = state.currentSignalStrengths.get(currentRat); + expectedDurationsMs[currentRat][currentFrequencyRange][currentSignalStrength] += inc; + }; + + state.setOnBattery(false); + state.setModemActive(false); + state.setRatType(TelephonyManager.NETWORK_TYPE_UNKNOWN, + BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER); + state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_UNKNOWN); + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER, + CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // While not on battery, the timers should not increase. + state.setModemActive(true); + incrementTime.accept(100); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR); + incrementTime.accept(200); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR, + CellSignalStrength.SIGNAL_STRENGTH_GOOD); + incrementTime.accept(500); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_MMWAVE); + incrementTime.accept(300); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setRatType(TelephonyManager.NETWORK_TYPE_LTE, + BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE); + incrementTime.accept(400); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + CellSignalStrength.SIGNAL_STRENGTH_MODERATE); + incrementTime.accept(500); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // When set on battery, currently active state (RAT:LTE, Signal Strength:Moderate) should + // start counting up. + state.setOnBattery(true); + incrementTime.accept(600); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Changing LTE signal strength should be tracked. + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + CellSignalStrength.SIGNAL_STRENGTH_POOR); + incrementTime.accept(700); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN); + incrementTime.accept(800); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + CellSignalStrength.SIGNAL_STRENGTH_GOOD); + incrementTime.accept(900); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE, + CellSignalStrength.SIGNAL_STRENGTH_GREAT); + incrementTime.accept(1000); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Change in the signal strength of nonactive RAT should not affect anything. + state.setSignalStrength(BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER, + CellSignalStrength.SIGNAL_STRENGTH_POOR); + incrementTime.accept(1100); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Changing to OTHER Rat should start tracking the poor signal strength. + state.setRatType(TelephonyManager.NETWORK_TYPE_CDMA, + BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER); + incrementTime.accept(1200); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Noting frequency change should not affect non NR Rat. + state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_HIGH); + incrementTime.accept(1300); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Now the NR Rat, HIGH frequency range, good signal strength should start counting. + state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR); + incrementTime.accept(1400); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Noting frequency change should not affect non NR Rat. + state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW); + incrementTime.accept(1500); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + // Modem no longer active, should not be tracking any more. + state.setModemActive(false); + incrementTime.accept(1500); + checkPerStateActiveRadioDurations(expectedDurationsMs, bi, state.currentTimeMs); + + } + private void setFgState(int uid, boolean fgOn, MockBatteryStatsImpl bi) { // Note that noteUidProcessStateLocked uses ActivityManager process states. if (fgOn) { @@ -1238,4 +1424,30 @@ public class BatteryStatsNoteTest extends TestCase { bi.getScreenBrightnessTime(bin, currentTimeMs * 1000, STATS_SINCE_CHARGED)); } } + + private void checkPerStateActiveRadioDurations(long[][][] expectedDurationsMs, + BatteryStatsImpl bi, long currentTimeMs) { + for (int rat = 0; rat < expectedDurationsMs.length; rat++) { + final long[][] expectedRatDurationsMs = expectedDurationsMs[rat]; + for (int freq = 0; freq < expectedRatDurationsMs.length; freq++) { + final long[] expectedFreqDurationsMs = expectedRatDurationsMs[freq]; + for (int strength = 0; strength < expectedFreqDurationsMs.length; strength++) { + final long expectedSignalStrengthDurationMs = expectedFreqDurationsMs[strength]; + final long actualDurationMs = bi.getActiveRadioDurationMs(rat, freq, + strength, currentTimeMs); + + // Build a verbose fail message, just in case. + final StringBuilder sb = new StringBuilder(); + sb.append("Wrong time in state for RAT:"); + sb.append(BatteryStats.RADIO_ACCESS_TECHNOLOGY_NAMES[rat]); + sb.append(", frequency:"); + sb.append(ServiceState.frequencyRangeToString(freq)); + sb.append(", strength:"); + sb.append(strength); + + assertEquals(sb.toString(), expectedSignalStrengthDurationMs, actualDurationMs); + } + } + } + } } diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java index db63e6e0b187..4c247427ef8f 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -38,7 +38,6 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; -import java.util.ArrayList; import java.util.HashSet; import java.util.Set; @@ -155,7 +154,7 @@ public final class DeviceStateManagerGlobalTest { verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE)); Mockito.reset(callback); - mDeviceStateManagerGlobal.cancelRequest(request); + mDeviceStateManagerGlobal.cancelStateRequest(); verify(callback).onStateChanged(eq(mService.getBaseState())); } @@ -172,7 +171,7 @@ public final class DeviceStateManagerGlobalTest { verify(callback).onRequestActivated(eq(request)); Mockito.reset(callback); - mDeviceStateManagerGlobal.cancelRequest(request); + mDeviceStateManagerGlobal.cancelStateRequest(); verify(callback).onRequestCanceled(eq(request)); } @@ -203,13 +202,13 @@ public final class DeviceStateManagerGlobalTest { private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE }; private int mBaseState = DEFAULT_DEVICE_STATE; - private ArrayList<Request> mRequests = new ArrayList<>(); + private Request mRequest; private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>(); private DeviceStateInfo getInfo() { - final int mergedState = mRequests.isEmpty() - ? mBaseState : mRequests.get(mRequests.size() - 1).state; + final int mergedState = mRequest == null + ? mBaseState : mRequest.state; return new DeviceStateInfo(mSupportedStates, mBaseState, mergedState); } @@ -245,11 +244,10 @@ public final class DeviceStateManagerGlobalTest { @Override public void requestState(IBinder token, int state, int flags) { - if (!mRequests.isEmpty()) { - final Request topRequest = mRequests.get(mRequests.size() - 1); + if (mRequest != null) { for (IDeviceStateManagerCallback callback : mCallbacks) { try { - callback.onRequestSuspended(topRequest.token); + callback.onRequestCanceled(mRequest.token); } catch (RemoteException e) { // Do nothing. Should never happen. } @@ -257,7 +255,7 @@ public final class DeviceStateManagerGlobalTest { } final Request request = new Request(token, state, flags); - mRequests.add(request); + mRequest = request; notifyDeviceStateInfoChanged(); for (IDeviceStateManagerCallback callback : mCallbacks) { @@ -270,20 +268,9 @@ public final class DeviceStateManagerGlobalTest { } @Override - public void cancelRequest(IBinder token) { - int index = -1; - for (int i = 0; i < mRequests.size(); i++) { - if (mRequests.get(i).token.equals(token)) { - index = i; - break; - } - } - - if (index == -1) { - throw new IllegalArgumentException("Unknown request: " + token); - } - - mRequests.remove(index); + public void cancelStateRequest() { + IBinder token = mRequest.token; + mRequest = null; for (IDeviceStateManagerCallback callback : mCallbacks) { try { callback.onRequestCanceled(token); diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java index f04a9f735881..e16a2f8e8560 100644 --- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java +++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java @@ -288,9 +288,8 @@ public class HdmiAudioSystemClientTest { } @Override - public void addVendorCommandListener(final IHdmiVendorCommandListener listener, - final int deviceType) { - } + public void addVendorCommandListener( + final IHdmiVendorCommandListener listener, final int vendorId) {} @Override public void sendVendorCommand(final int deviceType, final int targetAddress, diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index d95644a02e69..ae350ec547c3 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -74,5 +74,6 @@ <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" /> <permission name="android.permission.FORCE_STOP_PACKAGES" /> <permission name="android.permission.ACCESS_FPS_COUNTER" /> + <permission name="android.permission.CHANGE_CONFIGURATION" /> </privapp-permissions> </permissions> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 88920c865511..92fca3661fbc 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -231,18 +231,6 @@ targetSdk="29"> <new-permission name="android.permission.ACCESS_MEDIA_LOCATION" /> </split-permission> - <split-permission name="android.permission.READ_EXTERNAL_STORAGE" - targetSdk="33"> - <new-permission name="android.permission.READ_MEDIA_AUDIO" /> - </split-permission> - <split-permission name="android.permission.READ_EXTERNAL_STORAGE" - targetSdk="33"> - <new-permission name="android.permission.READ_MEDIA_VIDEO" /> - </split-permission> - <split-permission name="android.permission.READ_EXTERNAL_STORAGE" - targetSdk="33"> - <new-permission name="android.permission.READ_MEDIA_IMAGE" /> - </split-permission> <split-permission name="android.permission.BLUETOOTH" targetSdk="31"> <new-permission name="android.permission.BLUETOOTH_SCAN" /> diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 4ae0fc4ae6ed..5a3a033c1cc8 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -272,13 +272,13 @@ <!-- fallback fonts --> <family lang="und-Arab" variant="elegant"> - <font weight="400" style="normal" postScriptName="NotoNaskhArabic"> + <font weight="400" style="normal"> NotoNaskhArabic-Regular.ttf </font> <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font> </family> <family lang="und-Arab" variant="compact"> - <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI"> + <font weight="400" style="normal"> NotoNaskhArabicUI-Regular.ttf </font> <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font> @@ -329,7 +329,7 @@ <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf </font> <font weight="700" style="normal">NotoSansThai-Bold.ttf</font> - <font weight="400" style="normal" fallbackFor="serif" postScriptName="NotoSerifThai"> + <font weight="400" style="normal" fallbackFor="serif"> NotoSerifThai-Regular.ttf </font> <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font> @@ -923,16 +923,16 @@ <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font> </family> <family lang="und-Laoo" variant="elegant"> - <font weight="400" style="normal" postScriptName="NotoSansLao">NotoSansLao-Regular.ttf + <font weight="400" style="normal">NotoSansLao-Regular.ttf </font> <font weight="700" style="normal">NotoSansLao-Bold.ttf</font> - <font weight="400" style="normal" fallbackFor="serif" postScriptName="NotoSerifLao"> + <font weight="400" style="normal" fallbackFor="serif"> NotoSerifLao-Regular.ttf </font> <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font> </family> <family lang="und-Laoo" variant="compact"> - <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf + <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf </font> <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font> </family> @@ -1013,7 +1013,7 @@ </font> </family> <family lang="und-Cans"> - <font weight="400" style="normal" postScriptName="NotoSansCanadianAboriginal"> + <font weight="400" style="normal"> NotoSansCanadianAboriginal-Regular.ttf </font> </family> diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 055e5ad17def..857af11e4ca3 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -458,7 +458,7 @@ public final class Bitmap implements Parcelable { * No color information is stored. * With this configuration, each pixel requires 1 byte of memory. */ - ALPHA_8 (1), + ALPHA_8(1), /** * Each pixel is stored on 2 bytes and only the RGB channels are @@ -479,7 +479,7 @@ public final class Bitmap implements Parcelable { * short color = (R & 0x1f) << 11 | (G & 0x3f) << 5 | (B & 0x1f); * </pre> */ - RGB_565 (3), + RGB_565(3), /** * Each pixel is stored on 2 bytes. The three RGB color channels @@ -501,7 +501,7 @@ public final class Bitmap implements Parcelable { * it is advised to use {@link #ARGB_8888} instead. */ @Deprecated - ARGB_4444 (4), + ARGB_4444(4), /** * Each pixel is stored on 4 bytes. Each channel (RGB and alpha @@ -516,10 +516,10 @@ public final class Bitmap implements Parcelable { * int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff); * </pre> */ - ARGB_8888 (5), + ARGB_8888(5), /** - * Each pixels is stored on 8 bytes. Each channel (RGB and alpha + * Each pixel is stored on 8 bytes. Each channel (RGB and alpha * for translucency) is stored as a * {@link android.util.Half half-precision floating point value}. * @@ -531,7 +531,7 @@ public final class Bitmap implements Parcelable { * long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff); * </pre> */ - RGBA_F16 (6), + RGBA_F16(6), /** * Special configuration, when bitmap is stored only in graphic memory. @@ -540,13 +540,29 @@ public final class Bitmap implements Parcelable { * It is optimal for cases, when the only operation with the bitmap is to draw it on a * screen. */ - HARDWARE (7); + HARDWARE(7), + + /** + * Each pixel is stored on 4 bytes. Each RGB channel is stored with 10 bits of precision + * (1024 possible values). There is an additional alpha channel that is stored with 2 bits + * of precision (4 possible values). + * + * This configuration is suited for wide-gamut and HDR content which does not require alpha + * blending, such that the memory cost is the same as ARGB_8888 while enabling higher color + * precision. + * + * <p>Use this formula to pack into 32 bits:</p> + * <pre class="prettyprint"> + * int color = (A & 0x3) << 30 | (B & 0x3ff) << 20 | (G & 0x3ff) << 10 | (R & 0x3ff); + * </pre> + */ + RGBA_1010102(8); @UnsupportedAppUsage final int nativeInt; private static Config sConfigs[] = { - null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE + null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE, RGBA_1010102 }; Config(int ni) { @@ -1000,8 +1016,8 @@ public final class Bitmap implements Parcelable { * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. - * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to - * mark the bitmap as opaque. Doing so will clear the bitmap in black + * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be + * used to mark the bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. * * @throws IllegalArgumentException if the width or height are <= 0, or if @@ -1019,8 +1035,8 @@ public final class Bitmap implements Parcelable { * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. - * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to - * mark the bitmap as opaque. Doing so will clear the bitmap in black + * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be + * used to mark the bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16} * and {@link ColorSpace.Named#SRGB sRGB} or @@ -1050,8 +1066,8 @@ public final class Bitmap implements Parcelable { * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. - * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to - * mark the bitmap as opaque. Doing so will clear the bitmap in black + * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be + * used to mark the bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. * * @throws IllegalArgumentException if the width or height are <= 0, or if @@ -1074,8 +1090,8 @@ public final class Bitmap implements Parcelable { * @param width The width of the bitmap * @param height The height of the bitmap * @param config The bitmap config to create. - * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to - * mark the bitmap as opaque. Doing so will clear the bitmap in black + * @param hasAlpha If the bitmap is ARGB_8888, RGBA_16F, or RGBA_1010102 this flag can be + * used to mark the bitmap as opaque. Doing so will clear the bitmap in black * instead of transparent. * @param colorSpace The color space of the bitmap. If the config is {@link Config#RGBA_F16} * and {@link ColorSpace.Named#SRGB sRGB} or diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java index cdf746fc9900..f440b693a5b3 100644 --- a/identity/java/android/security/identity/IdentityCredential.java +++ b/identity/java/android/security/identity/IdentityCredential.java @@ -454,7 +454,8 @@ public abstract class IdentityCredential { * @param challenge is a non-empty byte array whose contents should be unique, fresh and * provided by the issuing authority. The value provided is embedded in the * generated CBOR and enables the issuing authority to verify that the - * returned proof is fresh. + * returned proof is fresh. Implementations are required to support + * challenges at least 32 bytes of length. * @return the COSE_Sign1 data structure above */ public @NonNull byte[] proveOwnership(@NonNull byte[] challenge) { @@ -485,7 +486,8 @@ public abstract class IdentityCredential { * @param challenge is a non-empty byte array whose contents should be unique, fresh and * provided by the issuing authority. The value provided is embedded in the * generated CBOR and enables the issuing authority to verify that the - * returned proof is fresh. + * returned proof is fresh. Implementations are required to support + * challenges at least 32 bytes of length. * @return the COSE_Sign1 data structure above */ public @NonNull byte[] delete(@NonNull byte[] challenge) { diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java index 305d0ead0652..6d569648f2c6 100644 --- a/identity/java/android/security/identity/WritableIdentityCredential.java +++ b/identity/java/android/security/identity/WritableIdentityCredential.java @@ -59,7 +59,8 @@ public abstract class WritableIdentityCredential { * @param challenge is a non-empty byte array whose contents should be unique, fresh and * provided by the issuing authority. The value provided is embedded in the * attestation extension and enables the issuing authority to verify that the - * attestation certificate is fresh. + * attestation certificate is fresh. Implementations are required to support + * challenges at least 32 bytes of length. * @return the X.509 certificate for this credential's CredentialKey. */ public abstract @NonNull Collection<X509Certificate> getCredentialKeyCertificateChain( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index e2bc36028405..9384e2b4dfdf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -245,7 +245,8 @@ public class DisplayController { } } - private void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) { + private void onKeepClearAreasChanged(int displayId, List<Rect> restricted, + List<Rect> unrestricted) { synchronized (mDisplays) { if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) { Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown" @@ -253,7 +254,8 @@ public class DisplayController { return; } for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { - mDisplayChangedListeners.get(i).onKeepClearAreasChanged(displayId, keepClearAreas); + mDisplayChangedListeners.get(i) + .onKeepClearAreasChanged(displayId, restricted, unrestricted); } } } @@ -318,9 +320,10 @@ public class DisplayController { } @Override - public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) { + public void onKeepClearAreasChanged(int displayId, List<Rect> restricted, + List<Rect> unrestricted) { mMainExecutor.execute(() -> { - DisplayController.this.onKeepClearAreasChanged(displayId, keepClearAreas); + DisplayController.this.onKeepClearAreasChanged(displayId, restricted, unrestricted); }); } } @@ -361,6 +364,7 @@ public class DisplayController { /** * Called when keep-clear areas on a display have changed. */ - default void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {} + default void onKeepClearAreasChanged(int displayId, List<Rect> restricted, + List<Rect> unrestricted) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index f61e62444366..9f4ff7c8dc06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -471,9 +471,10 @@ public abstract class WMShellBaseModule { static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool, DisplayController displayController, Context context, @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler, @ShellAnimationThread ShellExecutor animExecutor) { return new Transitions(organizer, pool, displayController, context, mainExecutor, - animExecutor); + mainHandler, animExecutor); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index eda09e3ce0b0..5ebdceba135b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -150,43 +150,45 @@ public class DragAndDropPolicy { if (inLandscape) { final Rect leftHitRegion = new Rect(); - final Rect leftDrawRegion = topOrLeftBounds; final Rect rightHitRegion = new Rect(); - final Rect rightDrawRegion = bottomOrRightBounds; // If we have existing split regions use those bounds, otherwise split it 50/50 if (inSplitScreen) { - // Add the divider bounds to each side since that counts for the hit region. - leftHitRegion.set(topOrLeftBounds); - leftHitRegion.right += dividerWidth / 2; - rightHitRegion.set(bottomOrRightBounds); - rightHitRegion.left -= dividerWidth / 2; + // The bounds of the existing split will have a divider bar, the hit region + // should include that space. Find the center of the divider bar: + float centerX = topOrLeftBounds.right + (dividerWidth / 2); + // Now set the hit regions using that center. + leftHitRegion.set(displayRegion); + leftHitRegion.right = (int) centerX; + rightHitRegion.set(displayRegion); + rightHitRegion.left = (int) centerX; } else { displayRegion.splitVertically(leftHitRegion, rightHitRegion); } - mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion)); - mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion)); + mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, topOrLeftBounds)); + mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, bottomOrRightBounds)); } else { final Rect topHitRegion = new Rect(); - final Rect topDrawRegion = topOrLeftBounds; final Rect bottomHitRegion = new Rect(); - final Rect bottomDrawRegion = bottomOrRightBounds; // If we have existing split regions use those bounds, otherwise split it 50/50 if (inSplitScreen) { - // Add the divider bounds to each side since that counts for the hit region. - topHitRegion.set(topOrLeftBounds); - topHitRegion.bottom += dividerWidth / 2; - bottomHitRegion.set(bottomOrRightBounds); - bottomHitRegion.top -= dividerWidth / 2; + // The bounds of the existing split will have a divider bar, the hit region + // should include that space. Find the center of the divider bar: + float centerX = topOrLeftBounds.bottom + (dividerWidth / 2); + // Now set the hit regions using that center. + topHitRegion.set(displayRegion); + topHitRegion.bottom = (int) centerX; + bottomHitRegion.set(displayRegion); + bottomHitRegion.top = (int) centerX; } else { displayRegion.splitHorizontally(topHitRegion, bottomHitRegion); } - mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion)); - mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion)); + mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topOrLeftBounds)); + mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomOrRightBounds)); } } else { // Split-screen not allowed, so only show the fullscreen target diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index fd3be2b11c15..7307ba30fd67 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -24,7 +24,6 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityTaskManager; @@ -103,7 +102,7 @@ public class DragLayout extends LinearLayout { MATCH_PARENT)); ((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1; ((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1; - updateContainerMargins(); + updateContainerMargins(getResources().getConfiguration().orientation); } @Override @@ -128,20 +127,18 @@ public class DragLayout extends LinearLayout { } public void onConfigChanged(Configuration newConfig) { - final int orientation = getResources().getConfiguration().orientation; - if (orientation == Configuration.ORIENTATION_LANDSCAPE + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE && getOrientation() != HORIZONTAL) { setOrientation(LinearLayout.HORIZONTAL); - updateContainerMargins(); - } else if (orientation == Configuration.ORIENTATION_PORTRAIT + updateContainerMargins(newConfig.orientation); + } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT && getOrientation() != VERTICAL) { setOrientation(LinearLayout.VERTICAL); - updateContainerMargins(); + updateContainerMargins(newConfig.orientation); } } - private void updateContainerMargins() { - final int orientation = getResources().getConfiguration().orientation; + private void updateContainerMargins(int orientation) { final float halfMargin = mDisplayMargin / 2f; if (orientation == Configuration.ORIENTATION_LANDSCAPE) { mDropZoneView1.setContainerMargin( @@ -156,10 +153,6 @@ public class DragLayout extends LinearLayout { } } - public boolean hasDropTarget() { - return mCurrentTarget != null; - } - public boolean hasDropped() { return mHasDropped; } @@ -271,6 +264,9 @@ public class DragLayout extends LinearLayout { * Updates the visible drop target as the user drags. */ public void update(DragEvent event) { + if (mHasDropped) { + return; + } // Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the // visibility of the current region DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation( @@ -286,7 +282,8 @@ public class DragLayout extends LinearLayout { animateHighlight(target); } else { // Switching between targets - animateHighlight(target); + mDropZoneView1.animateSwitch(); + mDropZoneView2.animateSwitch(); } mCurrentTarget = target; } @@ -323,7 +320,7 @@ public class DragLayout extends LinearLayout { : DISABLE_NONE); mDropZoneView1.setShowingMargin(visible); mDropZoneView2.setShowingMargin(visible); - ObjectAnimator animator = mDropZoneView1.getAnimator(); + Animator animator = mDropZoneView1.getAnimator(); if (animCompleteCallback != null) { if (animator != null) { animator.addListener(new AnimatorListenerAdapter() { @@ -343,17 +340,11 @@ public class DragLayout extends LinearLayout { if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_LEFT || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_TOP) { mDropZoneView1.setShowingHighlight(true); - mDropZoneView1.setShowingSplash(false); - mDropZoneView2.setShowingHighlight(false); - mDropZoneView2.setShowingSplash(true); } else if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM) { mDropZoneView1.setShowingHighlight(false); - mDropZoneView1.setShowingSplash(true); - mDropZoneView2.setShowingHighlight(true); - mDropZoneView2.setShowingSplash(false); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java index 2f47af57d496..a3ee8aed204d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java @@ -18,6 +18,7 @@ package com.android.wm.shell.draganddrop; import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN; +import android.animation.Animator; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; @@ -27,7 +28,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.FloatProperty; -import android.util.IntProperty; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -43,8 +43,8 @@ import com.android.wm.shell.R; */ public class DropZoneView extends FrameLayout { - private static final int SPLASHSCREEN_ALPHA_INT = (int) (255 * 0.90f); - private static final int HIGHLIGHT_ALPHA_INT = 255; + private static final float SPLASHSCREEN_ALPHA = 0.90f; + private static final float HIGHLIGHT_ALPHA = 1f; private static final int MARGIN_ANIMATION_ENTER_DURATION = 400; private static final int MARGIN_ANIMATION_EXIT_DURATION = 250; @@ -61,54 +61,27 @@ public class DropZoneView extends FrameLayout { } }; - private static final IntProperty<ColorDrawable> SPLASHSCREEN_ALPHA = - new IntProperty<ColorDrawable>("splashscreen") { - @Override - public void setValue(ColorDrawable d, int alpha) { - d.setAlpha(alpha); - } - - @Override - public Integer get(ColorDrawable d) { - return d.getAlpha(); - } - }; - - private static final IntProperty<ColorDrawable> HIGHLIGHT_ALPHA = - new IntProperty<ColorDrawable>("highlight") { - @Override - public void setValue(ColorDrawable d, int alpha) { - d.setAlpha(alpha); - } - - @Override - public Integer get(ColorDrawable d) { - return d.getAlpha(); - } - }; - private final Path mPath = new Path(); private final float[] mContainerMargin = new float[4]; private float mCornerRadius; private float mBottomInset; private int mMarginColor; // i.e. color used for negative space like the container insets - private int mHighlightColor; private boolean mShowingHighlight; private boolean mShowingSplash; private boolean mShowingMargin; - // TODO: might be more seamless to animate between splash/highlight color instead of 2 separate - private ObjectAnimator mSplashAnimator; - private ObjectAnimator mHighlightAnimator; + private int mSplashScreenColor; + private int mHighlightColor; + + private ObjectAnimator mBackgroundAnimator; private ObjectAnimator mMarginAnimator; private float mMarginPercent; // Renders a highlight or neutral transparent color - private ColorDrawable mDropZoneDrawable; + private ColorDrawable mColorDrawable; // Renders the translucent splashscreen with the app icon in the middle private ImageView mSplashScreenView; - private ColorDrawable mSplashBackgroundDrawable; // Renders the margin / insets around the dropzone container private MarginView mMarginView; @@ -130,19 +103,14 @@ public class DropZoneView extends FrameLayout { mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); mMarginColor = getResources().getColor(R.color.taskbar_background); - mHighlightColor = getResources().getColor(android.R.color.system_accent1_500); - - mDropZoneDrawable = new ColorDrawable(); - mDropZoneDrawable.setColor(mHighlightColor); - mDropZoneDrawable.setAlpha(0); - setBackgroundDrawable(mDropZoneDrawable); + int c = getResources().getColor(android.R.color.system_accent1_500); + mHighlightColor = Color.argb(HIGHLIGHT_ALPHA, Color.red(c), Color.green(c), Color.blue(c)); + mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, 0, 0, 0); + mColorDrawable = new ColorDrawable(); + setBackgroundDrawable(mColorDrawable); mSplashScreenView = new ImageView(context); mSplashScreenView.setScaleType(ImageView.ScaleType.CENTER); - mSplashBackgroundDrawable = new ColorDrawable(); - mSplashBackgroundDrawable.setColor(Color.WHITE); - mSplashBackgroundDrawable.setAlpha(SPLASHSCREEN_ALPHA_INT); - mSplashScreenView.setBackgroundDrawable(mSplashBackgroundDrawable); addView(mSplashScreenView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mSplashScreenView.setAlpha(0f); @@ -157,10 +125,6 @@ public class DropZoneView extends FrameLayout { mMarginColor = getResources().getColor(R.color.taskbar_background); mHighlightColor = getResources().getColor(android.R.color.system_accent1_500); - final int alpha = mDropZoneDrawable.getAlpha(); - mDropZoneDrawable.setColor(mHighlightColor); - mDropZoneDrawable.setAlpha(alpha); - if (mMarginPercent > 0) { mMarginView.invalidate(); } @@ -187,38 +151,39 @@ public class DropZoneView extends FrameLayout { } /** Sets the color and icon to use for the splashscreen when shown. */ - public void setAppInfo(int splashScreenColor, Drawable appIcon) { - mSplashBackgroundDrawable.setColor(splashScreenColor); + public void setAppInfo(int color, Drawable appIcon) { + Color c = Color.valueOf(color); + mSplashScreenColor = Color.argb(SPLASHSCREEN_ALPHA, c.red(), c.green(), c.blue()); mSplashScreenView.setImageDrawable(appIcon); } /** @return an active animator for this view if one exists. */ @Nullable - public ObjectAnimator getAnimator() { + public Animator getAnimator() { if (mMarginAnimator != null && mMarginAnimator.isRunning()) { return mMarginAnimator; - } else if (mHighlightAnimator != null && mHighlightAnimator.isRunning()) { - return mHighlightAnimator; - } else if (mSplashAnimator != null && mSplashAnimator.isRunning()) { - return mSplashAnimator; + } else if (mBackgroundAnimator != null && mBackgroundAnimator.isRunning()) { + return mBackgroundAnimator; } return null; } - /** Animates the splashscreen to show or hide. */ - public void setShowingSplash(boolean showingSplash) { - if (mShowingSplash != showingSplash) { - mShowingSplash = showingSplash; - animateSplashToState(); - } + /** Animates between highlight and splashscreen depending on current state. */ + public void animateSwitch() { + mShowingHighlight = !mShowingHighlight; + mShowingSplash = !mShowingHighlight; + final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor; + animateBackground(mColorDrawable.getColor(), newColor); + animateSplashScreenIcon(); } /** Animates the highlight indicating the zone is hovered on or not. */ public void setShowingHighlight(boolean showingHighlight) { - if (mShowingHighlight != showingHighlight) { - mShowingHighlight = showingHighlight; - animateHighlightToState(); - } + mShowingHighlight = showingHighlight; + mShowingSplash = !mShowingHighlight; + final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor; + animateBackground(Color.TRANSPARENT, newColor); + animateSplashScreenIcon(); } /** Animates the margins around the drop zone to show or hide. */ @@ -228,38 +193,29 @@ public class DropZoneView extends FrameLayout { animateMarginToState(); } if (!mShowingMargin) { - setShowingHighlight(false); - setShowingSplash(false); + mShowingHighlight = false; + mShowingSplash = false; + animateBackground(mColorDrawable.getColor(), Color.TRANSPARENT); + animateSplashScreenIcon(); } } - private void animateSplashToState() { - if (mSplashAnimator != null) { - mSplashAnimator.cancel(); + private void animateBackground(int startColor, int endColor) { + if (mBackgroundAnimator != null) { + mBackgroundAnimator.cancel(); } - mSplashAnimator = ObjectAnimator.ofInt(mSplashBackgroundDrawable, - SPLASHSCREEN_ALPHA, - mSplashBackgroundDrawable.getAlpha(), - mShowingSplash ? SPLASHSCREEN_ALPHA_INT : 0); - if (!mShowingSplash) { - mSplashAnimator.setInterpolator(FAST_OUT_SLOW_IN); + mBackgroundAnimator = ObjectAnimator.ofArgb(mColorDrawable, + "color", + startColor, + endColor); + if (!mShowingSplash && !mShowingHighlight) { + mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN); } - mSplashAnimator.start(); - mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start(); + mBackgroundAnimator.start(); } - private void animateHighlightToState() { - if (mHighlightAnimator != null) { - mHighlightAnimator.cancel(); - } - mHighlightAnimator = ObjectAnimator.ofInt(mDropZoneDrawable, - HIGHLIGHT_ALPHA, - mDropZoneDrawable.getAlpha(), - mShowingHighlight ? HIGHLIGHT_ALPHA_INT : 0); - if (!mShowingHighlight) { - mHighlightAnimator.setInterpolator(FAST_OUT_SLOW_IN); - } - mHighlightAnimator.start(); + private void animateSplashScreenIcon() { + mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start(); } private void animateMarginToState() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 17005ea7d500..6b0d7f5fa461 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -867,6 +867,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } private void fadeExistingPip(boolean show) { + if (mLeash == null || !mLeash.isValid()) { + Log.w(TAG, "Invalid leash on fadeExistingPip: " + mLeash); + return; + } final float alphaStart = show ? 0 : 1; final float alphaEnd = show ? 1 : 0; mPipAnimationController diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index e592101d2b20..a2c2f591cde0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -879,6 +879,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mMainUnfoldController != null && mSideUnfoldController != null) { mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible); mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible); + updateUnfoldBounds(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 79c8a87acb5b..ddf01a8c5ee9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -25,6 +25,11 @@ import static android.app.ActivityOptions.ANIM_SCALE_UP; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN; import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED; +import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE; +import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; +import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; +import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; @@ -57,16 +62,27 @@ import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Paint; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.hardware.HardwareBuffer; +import android.os.Handler; import android.os.IBinder; import android.os.SystemProperties; import android.os.UserHandle; import android.util.ArrayMap; import android.view.Choreographer; +import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; @@ -92,6 +108,8 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; /** The default handler that handles anything not already handled. */ public class DefaultTransitionHandler implements Transitions.TransitionHandler { @@ -118,6 +136,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; private final TransitionAnimation mTransitionAnimation; + private final DevicePolicyManager mDevicePolicyManager; private final SurfaceSession mSurfaceSession = new SurfaceSession(); @@ -132,9 +151,24 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { private ScreenRotationAnimation mRotationAnimation; + private Drawable mEnterpriseThumbnailDrawable; + + private BroadcastReceiver mEnterpriseResourceUpdatedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + boolean isDrawable = intent.getBooleanExtra( + EXTRA_RESOURCE_TYPE_DRAWABLE, /* default= */ false); + if (!isDrawable) { + return; + } + updateEnterpriseThumbnailDrawable(); + } + }; + DefaultTransitionHandler(@NonNull DisplayController displayController, @NonNull TransactionPool transactionPool, Context context, - @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { + @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, + @NonNull ShellExecutor animExecutor) { mDisplayController = displayController; mTransactionPool = transactionPool; mContext = context; @@ -143,9 +177,23 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG); mCurrentUserId = UserHandle.myUserId(); + mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); + updateEnterpriseThumbnailDrawable(); + mContext.registerReceiver( + mEnterpriseResourceUpdatedReceiver, + new IntentFilter(ACTION_DEVICE_POLICY_RESOURCE_UPDATED), + /* broadcastPermission = */ null, + mainHandler); + AttributeCache.init(context); } + private void updateEnterpriseThumbnailDrawable() { + mEnterpriseThumbnailDrawable = mDevicePolicyManager.getDrawable( + WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION, + () -> mContext.getDrawable(R.drawable.ic_corp_badge)); + } + @VisibleForTesting static boolean isRotationSeamless(@NonNull TransitionInfo info, DisplayController displayController) { @@ -290,6 +338,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); }; + final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks = + new ArrayList<>(); + @ColorInt int backgroundColorForTransition = 0; final int wallpaperTransit = getWallpaperTransitType(info); for (int i = info.getChanges().size() - 1; i >= 0; --i) { @@ -361,13 +412,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - float cornerRadius = 0; + final float cornerRadius; if (a.hasRoundedCorners() && isTask) { // hasRoundedCorners is currently only enabled for tasks final Context displayContext = mDisplayController.getDisplayContext(change.getTaskInfo().displayId); cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(displayContext); + } else { + cornerRadius = 0; } if (a.getShowBackground()) { @@ -383,12 +436,37 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } + boolean delayedEdgeExtension = false; + if (!isTask && a.hasExtension()) { + if (!Transitions.isOpeningType(change.getMode())) { + // Can screenshot now (before startTransaction is applied) + edgeExtendWindow(change, a, startTransaction, finishTransaction); + } else { + // Need to screenshot after startTransaction is applied otherwise activity + // may not be visible or ready yet. + postStartTransactionCallbacks + .add(t -> edgeExtendWindow(change, a, t, finishTransaction)); + delayedEdgeExtension = true; + } + } + final Rect clipRect = Transitions.isClosingType(change.getMode()) ? mRotator.getEndBoundsInStartRotation(change) : change.getEndAbsBounds(); - startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, - mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */, - cornerRadius, clipRect); + + if (delayedEdgeExtension) { + // If the edge extension needs to happen after the startTransition has been + // applied, then we want to only start the animation after the edge extension + // postStartTransaction callback has been run + postStartTransactionCallbacks.add(t -> + startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, + mTransactionPool, mMainExecutor, mAnimExecutor, + null /* position */, cornerRadius, clipRect)); + } else { + startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish, + mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */, + cornerRadius, clipRect); + } if (info.getAnimationOptions() != null) { attachThumbnail(animations, onAnimFinish, change, info.getAnimationOptions(), @@ -402,7 +480,20 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { startTransaction, finishTransaction); } - startTransaction.apply(); + // postStartTransactionCallbacks require that the start transaction is already + // applied to run otherwise they may result in flickers and UI inconsistencies. + boolean waitForStartTransactionApply = postStartTransactionCallbacks.size() > 0; + startTransaction.apply(waitForStartTransactionApply); + + // Run tasks that require startTransaction to already be applied + for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback : + postStartTransactionCallbacks) { + final SurfaceControl.Transaction t = mTransactionPool.acquire(); + postStartTransactionCallback.accept(t); + t.apply(); + mTransactionPool.release(t); + } + mRotator.cleanUp(finishTransaction); TransitionMetrics.getInstance().reportAnimationStart(transition); // run finish now in-case there are no animations @@ -410,6 +501,117 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return true; } + private void edgeExtendWindow(TransitionInfo.Change change, + Animation a, SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction) { + final Transformation transformationAtStart = new Transformation(); + a.getTransformationAt(0, transformationAtStart); + final Transformation transformationAtEnd = new Transformation(); + a.getTransformationAt(1, transformationAtEnd); + + // We want to create an extension surface that is the maximal size and the animation will + // take care of cropping any part that overflows. + final Insets maxExtensionInsets = Insets.min( + transformationAtStart.getInsets(), transformationAtEnd.getInsets()); + + final int targetSurfaceHeight = Math.max(change.getStartAbsBounds().height(), + change.getEndAbsBounds().height()); + final int targetSurfaceWidth = Math.max(change.getStartAbsBounds().width(), + change.getEndAbsBounds().width()); + if (maxExtensionInsets.left < 0) { + final Rect edgeBounds = new Rect(0, 0, 1, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + -maxExtensionInsets.left, targetSurfaceHeight); + final int xPos = maxExtensionInsets.left; + final int yPos = 0; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Left Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.top < 0) { + final Rect edgeBounds = new Rect(0, 0, targetSurfaceWidth, 1); + final Rect extensionRect = new Rect(0, 0, + targetSurfaceWidth, -maxExtensionInsets.top); + final int xPos = 0; + final int yPos = maxExtensionInsets.top; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Top Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.right < 0) { + final Rect edgeBounds = new Rect(targetSurfaceWidth - 1, 0, + targetSurfaceWidth, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + -maxExtensionInsets.right, targetSurfaceHeight); + final int xPos = targetSurfaceWidth; + final int yPos = 0; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Right Edge Extension", startTransaction, finishTransaction); + } + + if (maxExtensionInsets.bottom < 0) { + final Rect edgeBounds = new Rect(0, targetSurfaceHeight - 1, + targetSurfaceWidth, targetSurfaceHeight); + final Rect extensionRect = new Rect(0, 0, + targetSurfaceWidth, -maxExtensionInsets.bottom); + final int xPos = maxExtensionInsets.left; + final int yPos = targetSurfaceHeight; + createExtensionSurface(change.getLeash(), edgeBounds, extensionRect, xPos, yPos, + "Bottom Edge Extension", startTransaction, finishTransaction); + } + } + + private SurfaceControl createExtensionSurface(SurfaceControl surfaceToExtend, Rect edgeBounds, + Rect extensionRect, int xPos, int yPos, String layerName, + SurfaceControl.Transaction startTransaction, + SurfaceControl.Transaction finishTransaction) { + final SurfaceControl edgeExtensionLayer = new SurfaceControl.Builder() + .setName(layerName) + .setParent(surfaceToExtend) + .setHidden(true) + .setCallsite("DefaultTransitionHandler#startAnimation") + .setOpaque(true) + .setBufferSize(extensionRect.width(), extensionRect.height()) + .build(); + + SurfaceControl.LayerCaptureArgs captureArgs = + new SurfaceControl.LayerCaptureArgs.Builder(surfaceToExtend) + .setSourceCrop(edgeBounds) + .setFrameScale(1) + .setPixelFormat(PixelFormat.RGBA_8888) + .setChildrenOnly(true) + .setAllowProtected(true) + .build(); + final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer = + SurfaceControl.captureLayers(captureArgs); + + if (edgeBuffer == null) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Failed to capture edge of window."); + return null; + } + + android.graphics.BitmapShader shader = + new android.graphics.BitmapShader(edgeBuffer.asBitmap(), + android.graphics.Shader.TileMode.CLAMP, + android.graphics.Shader.TileMode.CLAMP); + final Paint paint = new Paint(); + paint.setShader(shader); + + final Surface surface = new Surface(edgeExtensionLayer); + Canvas c = surface.lockHardwareCanvas(); + c.drawRect(extensionRect, paint); + surface.unlockCanvasAndPost(c); + surface.release(); + + startTransaction.setLayer(edgeExtensionLayer, Integer.MIN_VALUE); + startTransaction.setPosition(edgeExtensionLayer, xPos, yPos); + startTransaction.setVisibility(edgeExtensionLayer, true); + finishTransaction.remove(edgeExtensionLayer); + + return edgeExtensionLayer; + } + private void addBackgroundToTransition( @NonNull SurfaceControl rootLeash, @ColorInt int color, @@ -632,7 +834,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final boolean isClose = Transitions.isClosingType(change.getMode()); if (isOpen) { if (options.getType() == ANIM_OPEN_CROSS_PROFILE_APPS && isTask) { - attachCrossProfileThunmbnailAnimation(animations, finishCallback, change, + attachCrossProfileThumbnailAnimation(animations, finishCallback, change, cornerRadius); } else if (options.getType() == ANIM_THUMBNAIL_SCALE_UP) { attachThumbnailAnimation(animations, finishCallback, change, options, cornerRadius); @@ -642,13 +844,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } - private void attachCrossProfileThunmbnailAnimation(@NonNull ArrayList<Animator> animations, + private void attachCrossProfileThumbnailAnimation(@NonNull ArrayList<Animator> animations, @NonNull Runnable finishCallback, TransitionInfo.Change change, float cornerRadius) { - final int thumbnailDrawableRes = change.getTaskInfo().userId == mCurrentUserId - ? R.drawable.ic_account_circle : R.drawable.ic_corp_badge; final Rect bounds = change.getEndAbsBounds(); + // Show the right drawable depending on the user we're transitioning to. + final Drawable thumbnailDrawable = change.getTaskInfo().userId == mCurrentUserId + ? mContext.getDrawable(R.drawable.ic_account_circle) : mEnterpriseThumbnailDrawable; final HardwareBuffer thumbnail = mTransitionAnimation.createCrossProfileAppsThumbnail( - thumbnailDrawableRes, bounds); + thumbnailDrawable, bounds); if (thumbnail == null) { return; } @@ -736,9 +939,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } t.setMatrix(leash, transformation.getMatrix(), matrix); t.setAlpha(leash, transformation.getAlpha()); + + Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE); + if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) { + // Clip out any overflowing edge extension + clipRect.inset(extensionInsets); + t.setCrop(leash, clipRect); + } + if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) { // We can only apply rounded corner if a crop is set - t.setWindowCrop(leash, clipRect); + t.setCrop(leash, clipRect); t.setCornerRadius(leash, cornerRadius); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 33a98b2fd80e..86b73fc30ca8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -33,6 +33,7 @@ import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; @@ -123,7 +124,8 @@ public class Transitions implements RemoteCallable<Transitions> { public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull DisplayController displayController, @NonNull Context context, - @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { + @NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler, + @NonNull ShellExecutor animExecutor) { mOrganizer = organizer; mContext = context; mMainExecutor = mainExecutor; @@ -132,7 +134,7 @@ public class Transitions implements RemoteCallable<Transitions> { mPlayerImpl = new TransitionPlayerImpl(); // The very last handler (0 in the list) should be the default one. mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor, - animExecutor)); + mainHandler, animExecutor)); // Next lowest priority is remote transitions. mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor); mHandlers.add(mRemoteTransitionHandler); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 825320b4e784..a6caefe6d3e7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -363,6 +363,45 @@ public class ShellTaskOrganizerTests { } @Test + public void testOnEligibleForLetterboxEducationActivityChanged() { + final RunningTaskInfo taskInfo1 = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN); + taskInfo1.displayId = DEFAULT_DISPLAY; + taskInfo1.topActivityEligibleForLetterboxEducation = false; + final TrackingTaskListener taskListener = new TrackingTaskListener(); + mOrganizer.addListenerForType(taskListener, TASK_LISTENER_TYPE_FULLSCREEN); + mOrganizer.onTaskAppeared(taskInfo1, null); + + // Task listener sent to compat UI is null if top activity isn't eligible for letterbox + // education. + verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */); + + // Task listener is non-null if top activity is eligible for letterbox education and task + // is visible. + clearInvocations(mCompatUI); + final RunningTaskInfo taskInfo2 = + createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN); + taskInfo2.displayId = taskInfo1.displayId; + taskInfo2.topActivityEligibleForLetterboxEducation = true; + taskInfo2.isVisible = true; + mOrganizer.onTaskInfoChanged(taskInfo2); + verify(mCompatUI).onCompatInfoChanged(taskInfo2, taskListener); + + // Task listener is null if task is invisible. + clearInvocations(mCompatUI); + final RunningTaskInfo taskInfo3 = + createTaskInfo(taskInfo1.taskId, WINDOWING_MODE_FULLSCREEN); + taskInfo3.displayId = taskInfo1.displayId; + taskInfo3.topActivityEligibleForLetterboxEducation = true; + taskInfo3.isVisible = false; + mOrganizer.onTaskInfoChanged(taskInfo3); + verify(mCompatUI).onCompatInfoChanged(taskInfo3, null /* taskListener */); + + clearInvocations(mCompatUI); + mOrganizer.onTaskVanished(taskInfo1); + verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */); + } + + @Test public void testOnCameraCompatActivityChanged() { final RunningTaskInfo taskInfo1 = createTaskInfo(1, WINDOWING_MODE_FULLSCREEN); taskInfo1.displayId = DEFAULT_DISPLAY; @@ -375,7 +414,7 @@ public class ShellTaskOrganizerTests { // compat control. verify(mCompatUI).onCompatInfoChanged(taskInfo1, null /* taskListener */); - // Task linster is non-null when request a camera compat control for a visible task. + // Task listener is non-null when request a camera compat control for a visible task. clearInvocations(mCompatUI); final RunningTaskInfo taskInfo2 = createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index e39171343bb9..0f4a06f22986 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -54,7 +54,9 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.view.IDisplayWindowListener; import android.view.IWindowManager; @@ -84,8 +86,6 @@ import com.android.wm.shell.common.TransactionPool; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import java.util.ArrayList; @@ -106,6 +106,7 @@ public class ShellTransitionTests { private final TestShellExecutor mMainExecutor = new TestShellExecutor(); private final ShellExecutor mAnimExecutor = new TestShellExecutor(); private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler(); + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); @Before public void setUp() { @@ -752,7 +753,7 @@ public class ShellTransitionTests { private Transitions createTestTransitions() { return new Transitions(mOrganizer, mTransactionPool, createTestDisplayController(), - mContext, mMainExecutor, mAnimExecutor); + mContext, mMainExecutor, mMainHandler, mAnimExecutor); } // // private class TestDisplayController extends DisplayController { diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index db3a1081e32c..dd272cd5ff7d 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -287,29 +287,29 @@ private: std::mutex mVkLock; }; -bool HardwareBitmapUploader::hasFP16Support() { - static std::once_flag sOnce; - static bool hasFP16Support = false; - - // Gralloc shouldn't let us create a USAGE_HW_TEXTURE if GLES is unable to consume it, so - // we don't need to double-check the GLES version/extension. - std::call_once(sOnce, []() { - AHardwareBuffer_Desc desc = { - .width = 1, - .height = 1, - .layers = 1, - .format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT, - .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | - AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER | - AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, - }; - UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc); - hasFP16Support = buffer != nullptr; - }); +static bool checkSupport(AHardwareBuffer_Format format) { + AHardwareBuffer_Desc desc = { + .width = 1, + .height = 1, + .layers = 1, + .format = format, + .usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER | AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER | + AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE, + }; + UniqueAHardwareBuffer buffer = allocateAHardwareBuffer(desc); + return buffer != nullptr; +} +bool HardwareBitmapUploader::hasFP16Support() { + static bool hasFP16Support = checkSupport(AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT); return hasFP16Support; } +bool HardwareBitmapUploader::has1010102Support() { + static bool has101012Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM); + return has101012Support; +} + static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) { FormatInfo formatInfo; switch (skBitmap.info().colorType()) { @@ -350,6 +350,19 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) { formatInfo.type = GL_UNSIGNED_BYTE; formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; break; + case kRGBA_1010102_SkColorType: + formatInfo.isSupported = HardwareBitmapUploader::has1010102Support(); + if (formatInfo.isSupported) { + formatInfo.type = GL_UNSIGNED_INT_2_10_10_10_REV; + formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM; + formatInfo.vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32; + } else { + formatInfo.type = GL_UNSIGNED_BYTE; + formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; + formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; + } + formatInfo.format = GL_RGBA; + break; default: ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType()); formatInfo.valid = false; diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h index ad7a95a4fa03..34f43cd8a198 100644 --- a/libs/hwui/HardwareBitmapUploader.h +++ b/libs/hwui/HardwareBitmapUploader.h @@ -29,10 +29,12 @@ public: #ifdef __ANDROID__ static bool hasFP16Support(); + static bool has1010102Support(); #else static bool hasFP16Support() { return true; } + static bool has1010102Support() { return true; } #endif }; diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp index 3780ba072308..bc6bc456ba5a 100644 --- a/libs/hwui/apex/android_bitmap.cpp +++ b/libs/hwui/apex/android_bitmap.cpp @@ -57,6 +57,8 @@ static AndroidBitmapFormat getFormat(const SkImageInfo& info) { return ANDROID_BITMAP_FORMAT_A_8; case kRGBA_F16_SkColorType: return ANDROID_BITMAP_FORMAT_RGBA_F16; + case kRGBA_1010102_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_1010102; default: return ANDROID_BITMAP_FORMAT_NONE; } @@ -74,6 +76,8 @@ static SkColorType getColorType(AndroidBitmapFormat format) { return kAlpha_8_SkColorType; case ANDROID_BITMAP_FORMAT_RGBA_F16: return kRGBA_F16_SkColorType; + case ANDROID_BITMAP_FORMAT_RGBA_1010102: + return kRGBA_1010102_SkColorType; default: return kUnknown_SkColorType; } @@ -249,6 +253,9 @@ int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const case ANDROID_BITMAP_FORMAT_RGBA_F16: colorType = kRGBA_F16_SkColorType; break; + case ANDROID_BITMAP_FORMAT_RGBA_1010102: + colorType = kRGBA_1010102_SkColorType; + break; default: return ANDROID_BITMAP_RESULT_BAD_PARAMETER; } diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp index fc542c81a597..dd68f825b61d 100644 --- a/libs/hwui/hwui/ImageDecoder.cpp +++ b/libs/hwui/hwui/ImageDecoder.cpp @@ -159,6 +159,8 @@ bool ImageDecoder::setOutColorType(SkColorType colorType) { break; case kRGBA_F16_SkColorType: break; + case kRGBA_1010102_SkColorType: + break; default: return false; } diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp index 4cc05ef6f13b..1c20415dcc8f 100644 --- a/libs/hwui/jni/BitmapRegionDecoder.cpp +++ b/libs/hwui/jni/BitmapRegionDecoder.cpp @@ -137,9 +137,16 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in auto* brd = reinterpret_cast<skia::BitmapRegionDecoder*>(brdHandle); SkColorType decodeColorType = brd->computeOutputColorType(colorType); - if (decodeColorType == kRGBA_F16_SkColorType && isHardware && + + if (isHardware) { + if (decodeColorType == kRGBA_F16_SkColorType && !uirenderer::HardwareBitmapUploader::hasFP16Support()) { - decodeColorType = kN32_SkColorType; + decodeColorType = kN32_SkColorType; + } + if (decodeColorType == kRGBA_1010102_SkColorType && + !uirenderer::HardwareBitmapUploader::has1010102Support()) { + decodeColorType = kN32_SkColorType; + } } // Set up the pixel allocator diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp index 77f46beb2100..33669ac0a34e 100644 --- a/libs/hwui/jni/Graphics.cpp +++ b/libs/hwui/jni/Graphics.cpp @@ -365,6 +365,8 @@ jint GraphicsJNI::colorTypeToLegacyBitmapConfig(SkColorType colorType) { return kRGB_565_LegacyBitmapConfig; case kAlpha_8_SkColorType: return kA8_LegacyBitmapConfig; + case kRGBA_1010102_SkColorType: + return kRGBA_1010102_LegacyBitmapConfig; case kUnknown_SkColorType: default: break; @@ -374,14 +376,10 @@ jint GraphicsJNI::colorTypeToLegacyBitmapConfig(SkColorType colorType) { SkColorType GraphicsJNI::legacyBitmapConfigToColorType(jint legacyConfig) { const uint8_t gConfig2ColorType[] = { - kUnknown_SkColorType, - kAlpha_8_SkColorType, - kUnknown_SkColorType, // Previously kIndex_8_SkColorType, - kRGB_565_SkColorType, - kARGB_4444_SkColorType, - kN32_SkColorType, - kRGBA_F16_SkColorType, - kN32_SkColorType + kUnknown_SkColorType, kAlpha_8_SkColorType, + kUnknown_SkColorType, // Previously kIndex_8_SkColorType, + kRGB_565_SkColorType, kARGB_4444_SkColorType, kN32_SkColorType, + kRGBA_F16_SkColorType, kN32_SkColorType, kRGBA_1010102_SkColorType, }; if (legacyConfig < 0 || legacyConfig > kLastEnum_LegacyBitmapConfig) { @@ -399,15 +397,12 @@ AndroidBitmapFormat GraphicsJNI::getFormatFromConfig(JNIEnv* env, jobject jconfi jint javaConfigId = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID); const AndroidBitmapFormat config2BitmapFormat[] = { - ANDROID_BITMAP_FORMAT_NONE, - ANDROID_BITMAP_FORMAT_A_8, - ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8 - ANDROID_BITMAP_FORMAT_RGB_565, - ANDROID_BITMAP_FORMAT_RGBA_4444, - ANDROID_BITMAP_FORMAT_RGBA_8888, - ANDROID_BITMAP_FORMAT_RGBA_F16, - ANDROID_BITMAP_FORMAT_NONE // Congfig.HARDWARE - }; + ANDROID_BITMAP_FORMAT_NONE, ANDROID_BITMAP_FORMAT_A_8, + ANDROID_BITMAP_FORMAT_NONE, // Previously Config.Index_8 + ANDROID_BITMAP_FORMAT_RGB_565, ANDROID_BITMAP_FORMAT_RGBA_4444, + ANDROID_BITMAP_FORMAT_RGBA_8888, ANDROID_BITMAP_FORMAT_RGBA_F16, + ANDROID_BITMAP_FORMAT_NONE, // Congfig.HARDWARE + ANDROID_BITMAP_FORMAT_RGBA_1010102}; return config2BitmapFormat[javaConfigId]; } @@ -430,6 +425,9 @@ jobject GraphicsJNI::getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format case ANDROID_BITMAP_FORMAT_RGBA_F16: configId = kRGBA_16F_LegacyBitmapConfig; break; + case ANDROID_BITMAP_FORMAT_RGBA_1010102: + configId = kRGBA_1010102_LegacyBitmapConfig; + break; default: break; } diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h index ba407f2164de..085a905abaf8 100644 --- a/libs/hwui/jni/GraphicsJNI.h +++ b/libs/hwui/jni/GraphicsJNI.h @@ -34,16 +34,17 @@ public: // This enum must keep these int values, to match the int values // in the java Bitmap.Config enum. enum LegacyBitmapConfig { - kNo_LegacyBitmapConfig = 0, - kA8_LegacyBitmapConfig = 1, - kIndex8_LegacyBitmapConfig = 2, - kRGB_565_LegacyBitmapConfig = 3, - kARGB_4444_LegacyBitmapConfig = 4, - kARGB_8888_LegacyBitmapConfig = 5, - kRGBA_16F_LegacyBitmapConfig = 6, - kHardware_LegacyBitmapConfig = 7, - - kLastEnum_LegacyBitmapConfig = kHardware_LegacyBitmapConfig + kNo_LegacyBitmapConfig = 0, + kA8_LegacyBitmapConfig = 1, + kIndex8_LegacyBitmapConfig = 2, + kRGB_565_LegacyBitmapConfig = 3, + kARGB_4444_LegacyBitmapConfig = 4, + kARGB_8888_LegacyBitmapConfig = 5, + kRGBA_16F_LegacyBitmapConfig = 6, + kHardware_LegacyBitmapConfig = 7, + kRGBA_1010102_LegacyBitmapConfig = 8, + + kLastEnum_LegacyBitmapConfig = kRGBA_1010102_LegacyBitmapConfig }; static void setJavaVM(JavaVM* javaVM); diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index 8ad8abcff2ed..25ed935b7a76 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -16,19 +16,18 @@ #ifndef DRAWFRAMETASK_H #define DRAWFRAMETASK_H -#include <optional> -#include <vector> - -#include <performance_hint_private.h> +#include <android/performance_hint.h> #include <utils/Condition.h> #include <utils/Mutex.h> #include <utils/StrongPointer.h> -#include "RenderTask.h" +#include <optional> +#include <vector> #include "../FrameInfo.h" #include "../Rect.h" #include "../TreeInfo.h" +#include "RenderTask.h" namespace android { namespace uirenderer { diff --git a/location/java/android/location/LastLocationRequest.java b/location/java/android/location/LastLocationRequest.java index 73c5c826584f..fe0a14f37cb6 100644 --- a/location/java/android/location/LastLocationRequest.java +++ b/location/java/android/location/LastLocationRequest.java @@ -16,6 +16,9 @@ package android.location; +import static android.Manifest.permission.LOCATION_BYPASS; +import static android.Manifest.permission.WRITE_SECURE_SETTINGS; + import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -220,8 +223,9 @@ public final class LastLocationRequest implements Parcelable { * * @hide */ + // TODO: remove WRITE_SECURE_SETTINGS. @SystemApi - @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS}) public @NonNull LastLocationRequest.Builder setAdasGnssBypass(boolean adasGnssBypass) { mAdasGnssBypass = adasGnssBypass; return this; @@ -238,8 +242,9 @@ public final class LastLocationRequest implements Parcelable { * * @hide */ + // TODO: remove WRITE_SECURE_SETTINGS. @SystemApi - @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS}) public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) { mLocationSettingsIgnored = locationSettingsIgnored; return this; diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index d275628f6e24..59c989b7f01e 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -18,6 +18,7 @@ package android.location; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.LOCATION_BYPASS; import static android.Manifest.permission.LOCATION_HARDWARE; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import static android.location.LocationRequest.createFromDeprecatedCriteria; @@ -678,8 +679,9 @@ public class LocationManager { * * @hide */ + // TODO: remove WRITE_SECURE_SETTINGS. @SystemApi - @RequiresPermission(WRITE_SECURE_SETTINGS) + @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS}) public void setAdasGnssLocationEnabled(boolean enabled) { try { mService.setAdasGnssLocationEnabledForUser(enabled, mContext.getUser().getIdentifier()); diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java index 587222a4df43..59f4f5e8c19e 100644 --- a/location/java/android/location/LocationRequest.java +++ b/location/java/android/location/LocationRequest.java @@ -16,6 +16,9 @@ package android.location; +import static android.Manifest.permission.LOCATION_BYPASS; +import static android.Manifest.permission.WRITE_SECURE_SETTINGS; + import static java.lang.Math.max; import static java.lang.Math.min; @@ -662,9 +665,10 @@ public final class LocationRequest implements Parcelable { * @hide * @deprecated LocationRequests should be treated as immutable. */ + // TODO: remove WRITE_SECURE_SETTINGS. @SystemApi @Deprecated - @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS}) public @NonNull LocationRequest setLocationSettingsIgnored(boolean locationSettingsIgnored) { mBypass = locationSettingsIgnored; return this; @@ -1132,8 +1136,9 @@ public final class LocationRequest implements Parcelable { * * @hide */ + // TODO: remove WRITE_SECURE_SETTINGS @SystemApi - @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS}) public @NonNull Builder setAdasGnssBypass(boolean adasGnssBypass) { mAdasGnssBypass = adasGnssBypass; return this; @@ -1150,8 +1155,9 @@ public final class LocationRequest implements Parcelable { * * @hide */ + // TODO: remove WRITE_SECURE_SETTINGS @SystemApi - @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + @RequiresPermission(anyOf = {WRITE_SECURE_SETTINGS, LOCATION_BYPASS}) public @NonNull Builder setLocationSettingsIgnored(boolean locationSettingsIgnored) { mBypass = locationSettingsIgnored; return this; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index a2704f9a67fd..15a398de9021 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -181,6 +181,22 @@ public class AudioManager { public static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION"; /** + * @hide Broadcast intent when the volume for a particular stream type changes. + * Includes the stream, the new volume and previous volumes. + * Notes: + * - for internal platform use only, do not make public, + * - never used for "remote" volume changes + * + * @see #EXTRA_VOLUME_STREAM_TYPE + * @see #EXTRA_VOLUME_STREAM_VALUE + * @see #EXTRA_PREV_VOLUME_STREAM_VALUE + */ + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @SuppressLint("ActionValue") + public static final String ACTION_VOLUME_CHANGED = "android.media.VOLUME_CHANGED_ACTION"; + + /** * @hide Broadcast intent when the devices for a particular stream type changes. * Includes the stream, the new devices and previous devices. * Notes: @@ -244,7 +260,8 @@ public class AudioManager { /** * @hide The stream type for the volume changed intent. */ - @UnsupportedAppUsage + @SystemApi + @SuppressLint("ActionValue") public static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"; /** @@ -261,7 +278,8 @@ public class AudioManager { /** * @hide The volume associated with the stream for the volume changed intent. */ - @UnsupportedAppUsage + @SystemApi + @SuppressLint("ActionValue") public static final String EXTRA_VOLUME_STREAM_VALUE = "android.media.EXTRA_VOLUME_STREAM_VALUE"; @@ -368,7 +386,7 @@ public class AudioManager { public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION; /** @hide Used to identify the volume of audio streams for phone calls when connected * to bluetooth */ - @UnsupportedAppUsage + @SystemApi public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO; /** @hide Used to identify the volume of audio streams for enforced system sounds * in certain countries (e.g camera in Japan) */ @@ -544,6 +562,7 @@ public class AudioManager { * Indicates the volume set/adjust call is for Bluetooth absolute volume * @hide */ + @SystemApi public static final int FLAG_BLUETOOTH_ABS_VOLUME = 1 << 6; /** diff --git a/media/java/android/media/BtProfileConnectionInfo.java b/media/java/android/media/BtProfileConnectionInfo.java index d1bb41e70b54..88b9777e911d 100644 --- a/media/java/android/media/BtProfileConnectionInfo.java +++ b/media/java/android/media/BtProfileConnectionInfo.java @@ -15,39 +15,25 @@ */ package android.media; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.bluetooth.BluetoothProfile; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Contains information about Bluetooth profile connection state changed * {@hide} */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public final class BtProfileConnectionInfo implements Parcelable { - /** @hide */ - @IntDef({ - BluetoothProfile.A2DP, - BluetoothProfile.A2DP_SINK, - BluetoothProfile.HEADSET, // Can only be set by BtHelper - BluetoothProfile.HEARING_AID, - BluetoothProfile.LE_AUDIO, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface BtProfile {} - - private final @BtProfile int mProfile; + + private final int mProfile; private final boolean mSupprNoisy; private final int mVolume; private final boolean mIsLeOutput; - private BtProfileConnectionInfo(@BtProfile int profile, boolean suppressNoisyIntent, int volume, + private BtProfileConnectionInfo(int profile, boolean suppressNoisyIntent, int volume, boolean isLeOutput) { mProfile = profile; mSupprNoisy = suppressNoisyIntent; @@ -59,7 +45,7 @@ public final class BtProfileConnectionInfo implements Parcelable { * Constructor used by BtHelper when a profile is connected * {@hide} */ - public BtProfileConnectionInfo(@BtProfile int profile) { + public BtProfileConnectionInfo(int profile) { this(profile, false, -1, false); } @@ -142,7 +128,7 @@ public final class BtProfileConnectionInfo implements Parcelable { /** * @return The profile connection */ - public @BtProfile int getProfile() { + public int getProfile() { return mProfile; } diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl index 5113dc2058e0..71dc2a781ba9 100644 --- a/media/java/android/media/IMediaRouter2Manager.aidl +++ b/media/java/android/media/IMediaRouter2Manager.aidl @@ -18,6 +18,7 @@ package android.media; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2Info; +import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; /** @@ -27,7 +28,8 @@ oneway interface IMediaRouter2Manager { void notifySessionCreated(int requestId, in RoutingSessionInfo session); void notifySessionUpdated(in RoutingSessionInfo session); void notifySessionReleased(in RoutingSessionInfo session); - void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures); + void notifyDiscoveryPreferenceChanged(String packageName, + in RouteDiscoveryPreference discoveryPreference); void notifyRoutesAdded(in List<MediaRoute2Info> routes); void notifyRoutesRemoved(in List<MediaRoute2Info> routes); void notifyRoutesChanged(in List<MediaRoute2Info> routes); diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 2427fa64562d..ee0293d629b1 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Describes the properties of a route. @@ -340,10 +341,12 @@ public final class MediaRoute2Info implements Parcelable { @ConnectionState final int mConnectionState; final String mClientPackageName; + final String mPackageName; final int mVolumeHandling; final int mVolumeMax; final int mVolume; final String mAddress; + final Set<String> mDeduplicationIds; final Bundle mExtras; final String mProviderId; @@ -357,10 +360,12 @@ public final class MediaRoute2Info implements Parcelable { mDescription = builder.mDescription; mConnectionState = builder.mConnectionState; mClientPackageName = builder.mClientPackageName; + mPackageName = builder.mPackageName; mVolumeHandling = builder.mVolumeHandling; mVolumeMax = builder.mVolumeMax; mVolume = builder.mVolume; mAddress = builder.mAddress; + mDeduplicationIds = builder.mDeduplicationIds; mExtras = builder.mExtras; mProviderId = builder.mProviderId; } @@ -375,10 +380,12 @@ public final class MediaRoute2Info implements Parcelable { mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mConnectionState = in.readInt(); mClientPackageName = in.readString(); + mPackageName = in.readString(); mVolumeHandling = in.readInt(); mVolumeMax = in.readInt(); mVolume = in.readInt(); mAddress = in.readString(); + mDeduplicationIds = Set.of(in.readStringArray()); mExtras = in.readBundle(); mProviderId = in.readString(); } @@ -486,6 +493,17 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Gets the package name of the provider that published the route. + * <p> + * It is set by the system service. + * @hide + */ + @Nullable + public String getPackageName() { + return mPackageName; + } + + /** * Gets information about how volume is handled on the route. * * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE} @@ -518,6 +536,18 @@ public final class MediaRoute2Info implements Parcelable { return mAddress; } + /** + * Gets the Deduplication ID of the route if available. + * @see RouteDiscoveryPreference#shouldRemoveDuplicates() + */ + @NonNull + public Set<String> getDeduplicationIds() { + return mDeduplicationIds; + } + + /** + * Gets an optional bundle with extra data. + */ @Nullable public Bundle getExtras() { return mExtras == null ? null : new Bundle(mExtras); @@ -549,7 +579,7 @@ public final class MediaRoute2Info implements Parcelable { * Returns if the route has at least one of the specified route features. * * @param features the list of route features to consider - * @return true if the route has at least one feature in the list + * @return {@code true} if the route has at least one feature in the list * @hide */ public boolean hasAnyFeatures(@NonNull Collection<String> features) { @@ -563,6 +593,21 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Returns if the route has all the specified route features. + * + * @hide + */ + public boolean hasAllFeatures(@NonNull Collection<String> features) { + Objects.requireNonNull(features, "features must not be null"); + for (String feature : features) { + if (!getFeatures().contains(feature)) { + return false; + } + } + return true; + } + + /** * Returns true if the route info has all of the required field. * A route is valid if and only if it is obtained from * {@link com.android.server.media.MediaRouterService}. @@ -596,10 +641,12 @@ public final class MediaRoute2Info implements Parcelable { && Objects.equals(mDescription, other.mDescription) && (mConnectionState == other.mConnectionState) && Objects.equals(mClientPackageName, other.mClientPackageName) + && Objects.equals(mPackageName, other.mPackageName) && (mVolumeHandling == other.mVolumeHandling) && (mVolumeMax == other.mVolumeMax) && (mVolume == other.mVolume) && Objects.equals(mAddress, other.mAddress) + && Objects.equals(mDeduplicationIds, other.mDeduplicationIds) && Objects.equals(mProviderId, other.mProviderId); } @@ -607,8 +654,8 @@ public final class MediaRoute2Info implements Parcelable { public int hashCode() { // Note: mExtras is not included. return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription, - mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume, - mAddress, mProviderId); + mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax, + mVolume, mAddress, mDeduplicationIds, mProviderId); } @Override @@ -626,6 +673,7 @@ public final class MediaRoute2Info implements Parcelable { .append(", volumeHandling=").append(getVolumeHandling()) .append(", volumeMax=").append(getVolumeMax()) .append(", volume=").append(getVolume()) + .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds())) .append(", providerId=").append(getProviderId()) .append(" }"); return result.toString(); @@ -647,10 +695,12 @@ public final class MediaRoute2Info implements Parcelable { TextUtils.writeToParcel(mDescription, dest, flags); dest.writeInt(mConnectionState); dest.writeString(mClientPackageName); + dest.writeString(mPackageName); dest.writeInt(mVolumeHandling); dest.writeInt(mVolumeMax); dest.writeInt(mVolume); dest.writeString(mAddress); + dest.writeStringArray(mDeduplicationIds.toArray(new String[mDeduplicationIds.size()])); dest.writeBundle(mExtras); dest.writeString(mProviderId); } @@ -671,10 +721,12 @@ public final class MediaRoute2Info implements Parcelable { @ConnectionState int mConnectionState; String mClientPackageName; + String mPackageName; int mVolumeHandling = PLAYBACK_VOLUME_FIXED; int mVolumeMax; int mVolume; String mAddress; + Set<String> mDeduplicationIds; Bundle mExtras; String mProviderId; @@ -698,6 +750,7 @@ public final class MediaRoute2Info implements Parcelable { mId = id; mName = name; mFeatures = new ArrayList<>(); + mDeduplicationIds = Set.of(); } /** @@ -733,10 +786,12 @@ public final class MediaRoute2Info implements Parcelable { mDescription = routeInfo.mDescription; mConnectionState = routeInfo.mConnectionState; mClientPackageName = routeInfo.mClientPackageName; + mPackageName = routeInfo.mPackageName; mVolumeHandling = routeInfo.mVolumeHandling; mVolumeMax = routeInfo.mVolumeMax; mVolume = routeInfo.mVolume; mAddress = routeInfo.mAddress; + mDeduplicationIds = Set.copyOf(routeInfo.mDeduplicationIds); if (routeInfo.mExtras != null) { mExtras = new Bundle(routeInfo.mExtras); } @@ -860,6 +915,16 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Sets the package name of the route. + * @hide + */ + @NonNull + public Builder setPackageName(@NonNull String packageName) { + mPackageName = packageName; + return this; + } + + /** * Sets the route's volume handling. */ @NonNull @@ -897,6 +962,20 @@ public final class MediaRoute2Info implements Parcelable { } /** + * Sets the deduplication ID of the route. + * Routes have the same ID could be removed even when + * they are from different providers. + * <p> + * If it's {@code null}, the route will not be removed. + * @see RouteDiscoveryPreference#shouldRemoveDuplicates() + */ + @NonNull + public Builder setDeduplicationIds(@NonNull Set<String> id) { + mDeduplicationIds = Set.copyOf(id); + return this; + } + + /** * Sets a bundle of extras for the route. * <p> * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}. diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 4b32dbfc42f5..b485eb51380d 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -34,15 +34,18 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; @@ -302,8 +305,7 @@ public final class MediaRouter2 { mSystemController = new SystemRoutingController( ensureClientPackageNameForSystemSession( sManager.getSystemRoutingSession(clientPackageName))); - mDiscoveryPreference = new RouteDiscoveryPreference.Builder( - sManager.getPreferredFeatures(clientPackageName), true).build(); + mDiscoveryPreference = sManager.getDiscoveryPreference(clientPackageName); updateAllRoutesFromManager(); // Only used by non-system MediaRouter2. @@ -1060,11 +1062,48 @@ public final class MediaRouter2 { .build(); } + private List<MediaRoute2Info> getSortedRoutes(List<MediaRoute2Info> routes, + RouteDiscoveryPreference preference) { + if (!preference.shouldRemoveDuplicates()) { + return routes; + } + Map<String, Integer> packagePriority = new ArrayMap<>(); + int count = preference.getDeduplicationPackageOrder().size(); + for (int i = 0; i < count; i++) { + // the last package will have 1 as the priority + packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i); + } + ArrayList<MediaRoute2Info> sortedRoutes = new ArrayList<>(routes); + // take the negative for descending order + sortedRoutes.sort(Comparator.comparingInt( + r -> -packagePriority.getOrDefault(r.getPackageName(), 0))); + return sortedRoutes; + } + private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes, - RouteDiscoveryPreference discoveryRequest) { - return routes.stream() - .filter(route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures())) - .collect(Collectors.toList()); + RouteDiscoveryPreference discoveryPreference) { + + Set<String> deduplicationIdSet = new ArraySet<>(); + + List<MediaRoute2Info> filteredRoutes = new ArrayList<>(); + for (MediaRoute2Info route : getSortedRoutes(routes, discoveryPreference)) { + if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures()) + || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) { + continue; + } + if (!discoveryPreference.getAllowedPackages().isEmpty() + && !discoveryPreference.getAllowedPackages().contains(route.getPackageName())) { + continue; + } + if (discoveryPreference.shouldRemoveDuplicates()) { + if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) { + continue; + } + deduplicationIdSet.addAll(route.getDeduplicationIds()); + } + filteredRoutes.add(route); + } + return filteredRoutes; } private void updateAllRoutesFromManager() { diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 83fa7c255f9f..8635c0ea762c 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -29,6 +29,8 @@ import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -36,15 +38,18 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -84,7 +89,8 @@ public final class MediaRouter2Manager { @GuardedBy("mRoutesLock") private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>(); @NonNull - final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>(); + final ConcurrentMap<String, RouteDiscoveryPreference> mDiscoveryPreferenceMap = + new ConcurrentHashMap<>(); private final AtomicInteger mNextRequestId = new AtomicInteger(1); private final CopyOnWriteArrayList<TransferRequest> mTransferRequests = @@ -247,25 +253,8 @@ public final class MediaRouter2Manager { */ @NonNull public List<MediaRoute2Info> getAvailableRoutes(@NonNull RoutingSessionInfo sessionInfo) { - Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); - - List<MediaRoute2Info> routes = new ArrayList<>(); - - String packageName = sessionInfo.getClientPackageName(); - List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName); - if (preferredFeatures == null) { - preferredFeatures = Collections.emptyList(); - } - synchronized (mRoutesLock) { - for (MediaRoute2Info route : mRoutes.values()) { - if (route.hasAnyFeatures(preferredFeatures) - || sessionInfo.getSelectedRoutes().contains(route.getId()) - || sessionInfo.getTransferableRoutes().contains(route.getId())) { - routes.add(route); - } - } - } - return routes; + return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/true, + null); } /** @@ -281,27 +270,70 @@ public final class MediaRouter2Manager { */ @NonNull public List<MediaRoute2Info> getTransferableRoutes(@NonNull RoutingSessionInfo sessionInfo) { + return getFilteredRoutes(sessionInfo, /*includeSelectedRoutes=*/false, + (route) -> sessionInfo.isSystemSession() ^ route.isSystemRoute()); + } + + private List<MediaRoute2Info> getSortedRoutes(RouteDiscoveryPreference preference) { + if (!preference.shouldRemoveDuplicates()) { + synchronized (mRoutesLock) { + return List.copyOf(mRoutes.values()); + } + } + Map<String, Integer> packagePriority = new ArrayMap<>(); + int count = preference.getDeduplicationPackageOrder().size(); + for (int i = 0; i < count; i++) { + // the last package will have 1 as the priority + packagePriority.put(preference.getDeduplicationPackageOrder().get(i), count - i); + } + ArrayList<MediaRoute2Info> routes; + synchronized (mRoutesLock) { + routes = new ArrayList<>(mRoutes.values()); + } + // take the negative for descending order + routes.sort(Comparator.comparingInt( + r -> -packagePriority.getOrDefault(r.getPackageName(), 0))); + return routes; + } + + private List<MediaRoute2Info> getFilteredRoutes(@NonNull RoutingSessionInfo sessionInfo, + boolean includeSelectedRoutes, + @Nullable Predicate<MediaRoute2Info> additionalFilter) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); List<MediaRoute2Info> routes = new ArrayList<>(); + Set<String> deduplicationIdSet = new ArraySet<>(); String packageName = sessionInfo.getClientPackageName(); - List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName); - if (preferredFeatures == null) { - preferredFeatures = Collections.emptyList(); - } - synchronized (mRoutesLock) { - for (MediaRoute2Info route : mRoutes.values()) { - if (sessionInfo.getTransferableRoutes().contains(route.getId())) { - routes.add(route); + RouteDiscoveryPreference discoveryPreference = + mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY); + + for (MediaRoute2Info route : getSortedRoutes(discoveryPreference)) { + if (sessionInfo.getTransferableRoutes().contains(route.getId()) + || (includeSelectedRoutes + && sessionInfo.getSelectedRoutes().contains(route.getId()))) { + routes.add(route); + continue; + } + if (!route.hasAllFeatures(discoveryPreference.getRequiredFeatures()) + || !route.hasAnyFeatures(discoveryPreference.getPreferredFeatures())) { + continue; + } + if (!discoveryPreference.getAllowedPackages().isEmpty() + && !discoveryPreference.getAllowedPackages() + .contains(route.getPackageName())) { + continue; + } + if (additionalFilter != null && !additionalFilter.test(route)) { + continue; + } + if (discoveryPreference.shouldRemoveDuplicates()) { + if (Collections.disjoint(deduplicationIdSet, route.getDeduplicationIds())) { continue; } - // Add Phone -> Cast and Cast -> Phone - if (route.hasAnyFeatures(preferredFeatures) - && (sessionInfo.isSystemSession() ^ route.isSystemRoute())) { - routes.add(route); - } + deduplicationIdSet.addAll(route.getDeduplicationIds()); } + routes.add(route); } return routes; } @@ -310,44 +342,10 @@ public final class MediaRouter2Manager { * Returns the preferred features of the specified package name. */ @NonNull - public List<String> getPreferredFeatures(@NonNull String packageName) { - Objects.requireNonNull(packageName, "packageName must not be null"); - - List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName); - if (preferredFeatures == null) { - preferredFeatures = Collections.emptyList(); - } - return preferredFeatures; - } - - /** - * Returns a list of routes which are related to the given package name in the given route list. - */ - @NonNull - public List<MediaRoute2Info> filterRoutesForPackage(@NonNull List<MediaRoute2Info> routes, - @NonNull String packageName) { - Objects.requireNonNull(routes, "routes must not be null"); + public RouteDiscoveryPreference getDiscoveryPreference(@NonNull String packageName) { Objects.requireNonNull(packageName, "packageName must not be null"); - List<RoutingSessionInfo> sessions = getRoutingSessions(packageName); - RoutingSessionInfo sessionInfo = sessions.get(sessions.size() - 1); - - List<MediaRoute2Info> result = new ArrayList<>(); - List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName); - if (preferredFeatures == null) { - preferredFeatures = Collections.emptyList(); - } - - synchronized (mRoutesLock) { - for (MediaRoute2Info route : routes) { - if (route.hasAnyFeatures(preferredFeatures) - || sessionInfo.getSelectedRoutes().contains(route.getId()) - || sessionInfo.getTransferableRoutes().contains(route.getId())) { - result.add(route); - } - } - } - return result; + return mDiscoveryPreferenceMap.getOrDefault(packageName, RouteDiscoveryPreference.EMPTY); } /** @@ -713,19 +711,19 @@ public final class MediaRouter2Manager { } } - void updatePreferredFeatures(String packageName, List<String> preferredFeatures) { - if (preferredFeatures == null) { - mPreferredFeaturesMap.remove(packageName); + void updateDiscoveryPreference(String packageName, RouteDiscoveryPreference preference) { + if (preference == null) { + mDiscoveryPreferenceMap.remove(packageName); return; } - List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures); - if ((prevFeatures == null && preferredFeatures.size() == 0) - || Objects.equals(preferredFeatures, prevFeatures)) { + RouteDiscoveryPreference prevPreference = + mDiscoveryPreferenceMap.put(packageName, preference); + if (Objects.equals(preference, prevPreference)) { return; } for (CallbackRecord record : mCallbackRecords) { record.mExecutor.execute(() -> record.mCallback - .onPreferredFeaturesChanged(packageName, preferredFeatures)); + .onDiscoveryPreferenceChanged(packageName, preference)); } } @@ -1047,6 +1045,17 @@ public final class MediaRouter2Manager { @NonNull List<String> preferredFeatures) {} /** + * Called when the preferred route features of an app is changed. + * + * @param packageName the package name of the application + * @param discoveryPreference the new discovery preference set by the application. + */ + default void onDiscoveryPreferenceChanged(@NonNull String packageName, + @NonNull RouteDiscoveryPreference discoveryPreference) { + onPreferredFeaturesChanged(packageName, discoveryPreference.getPreferredFeatures()); + } + + /** * Called when a previous request has failed. * * @param reason the reason that the request has failed. Can be one of followings: @@ -1125,9 +1134,10 @@ public final class MediaRouter2Manager { } @Override - public void notifyPreferredFeaturesChanged(String packageName, List<String> features) { - mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures, - MediaRouter2Manager.this, packageName, features)); + public void notifyDiscoveryPreferenceChanged(String packageName, + RouteDiscoveryPreference discoveryPreference) { + mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateDiscoveryPreference, + MediaRouter2Manager.this, packageName, discoveryPreference)); } @Override diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java index 37fee8466859..004501812ff6 100644 --- a/media/java/android/media/RouteDiscoveryPreference.java +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -64,6 +64,13 @@ public final class RouteDiscoveryPreference implements Parcelable { @NonNull private final List<String> mPreferredFeatures; + @NonNull + private final List<String> mRequiredFeatures; + @NonNull + private final List<String> mPackagesOrder; + @NonNull + private final List<String> mAllowedPackages; + private final boolean mShouldPerformActiveScan; @Nullable private final Bundle mExtras; @@ -78,12 +85,18 @@ public final class RouteDiscoveryPreference implements Parcelable { RouteDiscoveryPreference(@NonNull Builder builder) { mPreferredFeatures = builder.mPreferredFeatures; + mRequiredFeatures = builder.mRequiredFeatures; + mPackagesOrder = builder.mPackageOrder; + mAllowedPackages = builder.mAllowedPackages; mShouldPerformActiveScan = builder.mActiveScan; mExtras = builder.mExtras; } RouteDiscoveryPreference(@NonNull Parcel in) { mPreferredFeatures = in.createStringArrayList(); + mRequiredFeatures = in.createStringArrayList(); + mPackagesOrder = in.createStringArrayList(); + mAllowedPackages = in.createStringArrayList(); mShouldPerformActiveScan = in.readBoolean(); mExtras = in.readBundle(); } @@ -96,6 +109,8 @@ public final class RouteDiscoveryPreference implements Parcelable { * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO}, * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider. * </p> + * + * @see #getRequiredFeatures() */ @NonNull public List<String> getPreferredFeatures() { @@ -103,6 +118,47 @@ public final class RouteDiscoveryPreference implements Parcelable { } /** + * Gets the required features of routes that media router would like to discover. + * <p> + * Routes that have all the required features will be discovered. + * They may include predefined features such as + * {@link MediaRoute2Info#FEATURE_LIVE_AUDIO}, {@link MediaRoute2Info#FEATURE_LIVE_VIDEO}, + * or {@link MediaRoute2Info#FEATURE_REMOTE_PLAYBACK} or custom features defined by a provider. + * + * @see #getPreferredFeatures() + */ + @NonNull + public List<String> getRequiredFeatures() { + return mRequiredFeatures; + } + + /** + * Gets the ordered list of package names used to remove duplicate routes. + * <p> + * Duplicate route removal is enabled if the returned list is non-empty. Routes are deduplicated + * based on their {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}. If two routes + * have a deduplication ID in common, only the route from the provider whose package name is + * first in the provided list will remain. + * + * @see #shouldRemoveDuplicates() + */ + @NonNull + public List<String> getDeduplicationPackageOrder() { + return mPackagesOrder; + } + + /** + * Gets the list of allowed packages. + * <p> + * If it's not empty, it will only discover routes from the provider whose package name + * belongs to the list. + */ + @NonNull + public List<String> getAllowedPackages() { + return mAllowedPackages; + } + + /** * Gets whether active scanning should be performed. * <p> * If any of discovery preferences sets this as {@code true}, active scanning will @@ -114,6 +170,15 @@ public final class RouteDiscoveryPreference implements Parcelable { } /** + * Gets whether duplicate routes removal is enabled. + * + * @see #getDeduplicationPackageOrder() + */ + public boolean shouldRemoveDuplicates() { + return !mPackagesOrder.isEmpty(); + } + + /** * @hide */ public Bundle getExtras() { @@ -128,6 +193,9 @@ public final class RouteDiscoveryPreference implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeStringList(mPreferredFeatures); + dest.writeStringList(mRequiredFeatures); + dest.writeStringList(mPackagesOrder); + dest.writeStringList(mAllowedPackages); dest.writeBoolean(mShouldPerformActiveScan); dest.writeBundle(mExtras); } @@ -155,14 +223,17 @@ public final class RouteDiscoveryPreference implements Parcelable { return false; } RouteDiscoveryPreference other = (RouteDiscoveryPreference) o; - //TODO: Make this order-free return Objects.equals(mPreferredFeatures, other.mPreferredFeatures) + && Objects.equals(mRequiredFeatures, other.mRequiredFeatures) + && Objects.equals(mPackagesOrder, other.mPackagesOrder) + && Objects.equals(mAllowedPackages, other.mAllowedPackages) && mShouldPerformActiveScan == other.mShouldPerformActiveScan; } @Override public int hashCode() { - return Objects.hash(mPreferredFeatures, mShouldPerformActiveScan); + return Objects.hash(mPreferredFeatures, mRequiredFeatures, mPackagesOrder, mAllowedPackages, + mShouldPerformActiveScan); } /** @@ -170,13 +241,21 @@ public final class RouteDiscoveryPreference implements Parcelable { */ public static final class Builder { List<String> mPreferredFeatures; + List<String> mRequiredFeatures; + List<String> mPackageOrder; + List<String> mAllowedPackages; + boolean mActiveScan; + Bundle mExtras; public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) { Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null"); mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str)) .collect(Collectors.toList()); + mRequiredFeatures = List.of(); + mPackageOrder = List.of(); + mAllowedPackages = List.of(); mActiveScan = activeScan; } @@ -184,12 +263,15 @@ public final class RouteDiscoveryPreference implements Parcelable { Objects.requireNonNull(preference, "preference must not be null"); mPreferredFeatures = preference.getPreferredFeatures(); + mRequiredFeatures = preference.getRequiredFeatures(); + mPackageOrder = preference.getDeduplicationPackageOrder(); + mAllowedPackages = preference.getAllowedPackages(); mActiveScan = preference.shouldPerformActiveScan(); mExtras = preference.getExtras(); } /** - * A constructor to combine all of the preferences into a single preference. + * A constructor to combine all the preferences into a single preference. * It ignores extras of preferences. * * @hide @@ -224,6 +306,30 @@ public final class RouteDiscoveryPreference implements Parcelable { } /** + * Sets the required route features to discover. + */ + @NonNull + public Builder setRequiredFeatures(@NonNull List<String> requiredFeatures) { + Objects.requireNonNull(requiredFeatures, "preferredFeatures must not be null"); + mRequiredFeatures = requiredFeatures.stream().filter(str -> !TextUtils.isEmpty(str)) + .collect(Collectors.toList()); + return this; + } + + /** + * Sets the list of package names of providers that media router would like to discover. + * <p> + * If it's non-empty, media router only discovers route from the provider in the list. + * The default value is empty, which discovers routes from all providers. + */ + @NonNull + public Builder setAllowedPackages(@NonNull List<String> allowedPackages) { + Objects.requireNonNull(allowedPackages, "allowedPackages must not be null"); + mAllowedPackages = List.copyOf(allowedPackages); + return this; + } + + /** * Sets if active scanning should be performed. * <p> * Since active scanning uses more system resources, set this as {@code true} only @@ -237,6 +343,24 @@ public final class RouteDiscoveryPreference implements Parcelable { } /** + * Sets the order of packages to use when removing duplicate routes. + * <p> + * Routes are deduplicated based on their + * {@link MediaRoute2Info#getDeduplicationIds() deduplication IDs}. + * If two routes have a deduplication ID in common, only the route from the provider whose + * package name is first in the provided list will remain. + * + * @param packageOrder ordered list of package names used to remove duplicate routes, or an + * empty list if deduplication should not be enabled. + */ + @NonNull + public Builder setDeduplicationPackageOrder(@NonNull List<String> packageOrder) { + Objects.requireNonNull(packageOrder, "packageOrder must not be null"); + mPackageOrder = List.copyOf(packageOrder); + return this; + } + + /** * Sets the extras of the route. * @hide */ diff --git a/media/native/midi/amidi.cpp b/media/native/midi/amidi.cpp index aa076e85e30d..fd8a06da5c6d 100644 --- a/media/native/midi/amidi.cpp +++ b/media/native/midi/amidi.cpp @@ -401,10 +401,14 @@ ssize_t AMIDI_API AMidiInputPort_send(const AMidiInputPort *inputPort, const uin ssize_t AMIDI_API AMidiInputPort_sendWithTimestamp(const AMidiInputPort *inputPort, const uint8_t *data, size_t numBytes, int64_t timestamp) { - if (inputPort == nullptr || data == nullptr) { + if (inputPort == nullptr || data == nullptr || numBytes < 0 || timestamp < 0) { return AMEDIA_ERROR_INVALID_PARAMETER; } + if (numBytes == 0) { + return 0; + } + // AMIDI_logBuffer(data, numBytes); uint8_t writeBuffer[AMIDI_BUFFER_SIZE + AMIDI_PACKET_OVERHEAD]; diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 35c794e82695..f9a17746892e 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -315,18 +315,18 @@ LIBANDROID { AThermal_registerThermalStatusListener; # introduced=30 AThermal_unregisterThermalStatusListener; # introduced=30 AThermal_getThermalHeadroom; # introduced=31 + APerformanceHint_getManager; # introduced=Tiramisu + APerformanceHint_createSession; # introduced=Tiramisu + APerformanceHint_getPreferredUpdateRateNanos; # introduced=Tiramisu + APerformanceHint_updateTargetWorkDuration; # introduced=Tiramisu + APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu + APerformanceHint_closeSession; # introduced=Tiramisu local: *; }; LIBANDROID_PLATFORM { global: - APerformanceHint_getManager; - APerformanceHint_createSession; - APerformanceHint_getPreferredUpdateRateNanos; - APerformanceHint_updateTargetWorkDuration; - APerformanceHint_reportActualWorkDuration; - APerformanceHint_closeSession; APerformanceHint_setIHintManagerForTesting; extern "C++" { ASurfaceControl_registerSurfaceStatsListener*; diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index 51a0c99af66e..0c360519ceb2 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -16,17 +16,18 @@ #define LOG_TAG "perf_hint" -#include <utility> -#include <vector> - #include <android/os/IHintManager.h> #include <android/os/IHintSession.h> +#include <android/performance_hint.h> #include <binder/Binder.h> #include <binder/IBinder.h> #include <binder/IServiceManager.h> #include <performance_hint_private.h> #include <utils/SystemClock.h> +#include <utility> +#include <vector> + using namespace android; using namespace android::os; diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index 284e9ee909ee..b17850e5d1e4 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -18,10 +18,12 @@ #include <android/os/IHintManager.h> #include <android/os/IHintSession.h> +#include <android/performance_hint.h> #include <binder/IBinder.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <performance_hint_private.h> + #include <memory> #include <vector> diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp index a0f3098ad347..bb25274e3136 100644 --- a/native/graphics/jni/imagedecoder.cpp +++ b/native/graphics/jni/imagedecoder.cpp @@ -198,14 +198,16 @@ static SkColorType getColorType(AndroidBitmapFormat format) { return kGray_8_SkColorType; case ANDROID_BITMAP_FORMAT_RGBA_F16: return kRGBA_F16_SkColorType; + case ANDROID_BITMAP_FORMAT_RGBA_1010102: + return kRGBA_1010102_SkColorType; default: return kUnknown_SkColorType; } } int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) { - if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE - || format > ANDROID_BITMAP_FORMAT_RGBA_F16) { + if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE || + format > ANDROID_BITMAP_FORMAT_RGBA_1010102) { return ANDROID_IMAGE_DECODER_BAD_PARAMETER; } @@ -290,6 +292,8 @@ static AndroidBitmapFormat getFormat(SkColorType colorType) { return ANDROID_BITMAP_FORMAT_A_8; case kRGBA_F16_SkColorType: return ANDROID_BITMAP_FORMAT_RGBA_F16; + case kRGBA_1010102_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_1010102; default: return ANDROID_BITMAP_FORMAT_NONE; } diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp index 223bdcdd9c95..327b1fb2f5d8 100644 --- a/packages/ConnectivityT/framework-t/Android.bp +++ b/packages/ConnectivityT/framework-t/Android.bp @@ -39,7 +39,6 @@ filegroup { "src/android/net/TrafficStats.java", "src/android/net/UnderlyingNetworkInfo.*", "src/android/net/netstats/**/*.*", - "src/com/android/server/NetworkManagementSocketTagger.java", ], path: "src", visibility: [ @@ -176,3 +175,34 @@ filegroup { "//packages/modules/Connectivity:__subpackages__", ], } + +cc_library_shared { + name: "libframework-connectivity-tiramisu-jni", + min_sdk_version: "30", + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + // Don't warn about S API usage even with + // min_sdk 30: the library is only loaded + // on S+ devices + "-Wno-unguarded-availability", + "-Wthread-safety", + ], + srcs: [ + "jni/android_net_TrafficStats.cpp", + "jni/onload.cpp", + ], + shared_libs: [ + "liblog", + ], + static_libs: [ + "libnativehelper_compat_libc++", + ], + stl: "none", + apex_available: [ + "com.android.tethering", + // TODO: remove when ConnectivityT moves to APEX. + "//apex_available:platform", + ], +} diff --git a/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp b/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp new file mode 100644 index 000000000000..f3c58b112f0d --- /dev/null +++ b/packages/ConnectivityT/framework-t/jni/android_net_TrafficStats.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/file_descriptor_jni.h> +#include <android/multinetwork.h> +#include <nativehelper/JNIHelp.h> + +namespace android { + +static jint tagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor, jint tag, jint uid) { + int fd = AFileDescriptor_getFd(env, fileDescriptor); + if (fd == -1) return -EBADF; + return android_tag_socket_with_uid(fd, tag, uid); +} + +static jint untagSocketFd(JNIEnv* env, jclass, jobject fileDescriptor) { + int fd = AFileDescriptor_getFd(env, fileDescriptor); + if (fd == -1) return -EBADF; + return android_untag_socket(fd); +} + +static const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "native_tagSocketFd", "(Ljava/io/FileDescriptor;II)I", (void*) tagSocketFd }, + { "native_untagSocketFd", "(Ljava/io/FileDescriptor;)I", (void*) untagSocketFd }, +}; + +int register_android_net_TrafficStats(JNIEnv* env) { + return jniRegisterNativeMethods(env, "android/net/TrafficStats", gMethods, NELEM(gMethods)); +} + +}; // namespace android + diff --git a/packages/ConnectivityT/framework-t/jni/onload.cpp b/packages/ConnectivityT/framework-t/jni/onload.cpp new file mode 100644 index 000000000000..1fb42c63477e --- /dev/null +++ b/packages/ConnectivityT/framework-t/jni/onload.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "FrameworkConnectivityJNI" + +#include <log/log.h> +#include <nativehelper/JNIHelp.h> + +namespace android { + +int register_android_net_TrafficStats(JNIEnv* env); + +extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { + JNIEnv *env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed"); + return JNI_ERR; + } + + if (register_android_net_TrafficStats(env) < 0) return JNI_ERR; + + return JNI_VERSION_1_6; +} + +}; // namespace android + diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java index 84adef53e488..5ce7e59b38ff 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java @@ -41,6 +41,7 @@ import android.net.NetworkStateSnapshot; import android.net.NetworkTemplate; import android.net.UnderlyingNetworkInfo; import android.net.netstats.IUsageCallback; +import android.net.netstats.NetworkStatsDataMigrationUtils; import android.net.netstats.provider.INetworkStatsProviderCallback; import android.net.netstats.provider.NetworkStatsProvider; import android.os.Build; @@ -126,17 +127,12 @@ public class NetworkStatsManager { private final INetworkStatsService mService; /** - * Type constants for reading different types of Data Usage. + * @deprecated Use {@link NetworkStatsDataMigrationUtils#PREFIX_XT} + * instead. * @hide */ - // @SystemApi(client = MODULE_LIBRARIES) + @Deprecated public static final String PREFIX_DEV = "dev"; - /** @hide */ - public static final String PREFIX_XT = "xt"; - /** @hide */ - public static final String PREFIX_UID = "uid"; - /** @hide */ - public static final String PREFIX_UID_TAG = "uid_tag"; /** @hide */ public static final int FLAG_POLL_ON_OPEN = 1 << 0; diff --git a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java index 7f4e403f2259..798e9c3b52b5 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java +++ b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java @@ -20,22 +20,34 @@ import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** @hide */ public final class InternalNetworkManagementException extends RuntimeException implements Parcelable { /* @hide */ - public InternalNetworkManagementException(@NonNull final Throwable t) { - super(t); + public InternalNetworkManagementException(@NonNull final String errorMessage) { + super(errorMessage); + } + + @Override + public int hashCode() { + return Objects.hash(getMessage()); } - private InternalNetworkManagementException(@NonNull final Parcel source) { - super(source.readString()); + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + final InternalNetworkManagementException that = (InternalNetworkManagementException) obj; + + return Objects.equals(getMessage(), that.getMessage()); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(getCause().getMessage()); + dest.writeString(getMessage()); } @Override @@ -53,7 +65,7 @@ public final class InternalNetworkManagementException @Override public InternalNetworkManagementException createFromParcel(@NonNull Parcel source) { - return new InternalNetworkManagementException(source); + return new InternalNetworkManagementException(source.readString()); } }; } diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java index c2f0cdfb048c..bc836d857e3e 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java +++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java @@ -31,12 +31,9 @@ import android.media.MediaPlayer; import android.os.Binder; import android.os.Build; import android.os.RemoteException; +import android.os.StrictMode; import android.util.Log; -import com.android.server.NetworkManagementSocketTagger; - -import dalvik.system.SocketTagger; - import java.io.FileDescriptor; import java.io.IOException; import java.net.DatagramSocket; @@ -56,6 +53,10 @@ import java.net.SocketException; * use {@link NetworkStatsManager} instead. */ public class TrafficStats { + static { + System.loadLibrary("framework-connectivity-tiramisu-jni"); + } + private static final String TAG = TrafficStats.class.getSimpleName(); /** * The return value to indicate that the device does not support the statistic. @@ -232,9 +233,68 @@ public class TrafficStats { */ @SystemApi(client = MODULE_LIBRARIES) public static void attachSocketTagger() { - NetworkManagementSocketTagger.install(); + dalvik.system.SocketTagger.set(new SocketTagger()); + } + + private static class SocketTagger extends dalvik.system.SocketTagger { + + // TODO: set to false + private static final boolean LOGD = true; + + SocketTagger() { + } + + @Override + public void tag(FileDescriptor fd) throws SocketException { + final UidTag tagInfo = sThreadUidTag.get(); + if (LOGD) { + Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x" + + Integer.toHexString(tagInfo.tag) + ", statsUid=" + tagInfo.uid); + } + if (tagInfo.tag == -1) { + StrictMode.noteUntaggedSocket(); + } + + if (tagInfo.tag == -1 && tagInfo.uid == -1) return; + final int errno = native_tagSocketFd(fd, tagInfo.tag, tagInfo.uid); + if (errno < 0) { + Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", " + + tagInfo.tag + ", " + + tagInfo.uid + ") failed with errno" + errno); + } + } + + @Override + public void untag(FileDescriptor fd) throws SocketException { + if (LOGD) { + Log.i(TAG, "untagSocket(" + fd.getInt$() + ")"); + } + + final UidTag tagInfo = sThreadUidTag.get(); + if (tagInfo.tag == -1 && tagInfo.uid == -1) return; + + final int errno = native_untagSocketFd(fd); + if (errno < 0) { + Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno); + } + } + } + + private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid); + private static native int native_untagSocketFd(FileDescriptor fd); + + private static class UidTag { + public int tag = -1; + public int uid = -1; } + private static ThreadLocal<UidTag> sThreadUidTag = new ThreadLocal<UidTag>() { + @Override + protected UidTag initialValue() { + return new UidTag(); + } + }; + /** * Set active tag to use when accounting {@link Socket} traffic originating * from the current thread. Only one active tag per thread is supported. @@ -249,7 +309,7 @@ public class TrafficStats { * @see #clearThreadStatsTag() */ public static void setThreadStatsTag(int tag) { - NetworkManagementSocketTagger.setThreadSocketStatsTag(tag); + getAndSetThreadStatsTag(tag); } /** @@ -267,7 +327,9 @@ public class TrafficStats { * restore any existing values after a nested operation is finished */ public static int getAndSetThreadStatsTag(int tag) { - return NetworkManagementSocketTagger.setThreadSocketStatsTag(tag); + final int old = sThreadUidTag.get().tag; + sThreadUidTag.get().tag = tag; + return old; } /** @@ -327,7 +389,7 @@ public class TrafficStats { * @see #setThreadStatsTag(int) */ public static int getThreadStatsTag() { - return NetworkManagementSocketTagger.getThreadSocketStatsTag(); + return sThreadUidTag.get().tag; } /** @@ -337,7 +399,7 @@ public class TrafficStats { * @see #setThreadStatsTag(int) */ public static void clearThreadStatsTag() { - NetworkManagementSocketTagger.setThreadSocketStatsTag(-1); + sThreadUidTag.get().tag = -1; } /** @@ -357,7 +419,7 @@ public class TrafficStats { */ @SuppressLint("RequiresPermission") public static void setThreadStatsUid(int uid) { - NetworkManagementSocketTagger.setThreadSocketStatsUid(uid); + sThreadUidTag.get().uid = uid; } /** @@ -368,7 +430,7 @@ public class TrafficStats { * @see #setThreadStatsUid(int) */ public static int getThreadStatsUid() { - return NetworkManagementSocketTagger.getThreadSocketStatsUid(); + return sThreadUidTag.get().uid; } /** @@ -395,7 +457,7 @@ public class TrafficStats { */ @SuppressLint("RequiresPermission") public static void clearThreadStatsUid() { - NetworkManagementSocketTagger.setThreadSocketStatsUid(-1); + setThreadStatsUid(-1); } /** diff --git a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java deleted file mode 100644 index 8bb12a6defe5..000000000000 --- a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.os.StrictMode; -import android.util.Log; - -import dalvik.system.SocketTagger; - -import java.io.FileDescriptor; -import java.net.SocketException; - -/** - * Assigns tags to sockets for traffic stats. - * @hide - */ -public final class NetworkManagementSocketTagger extends SocketTagger { - private static final String TAG = "NetworkManagementSocketTagger"; - private static final boolean LOGD = false; - - private static ThreadLocal<SocketTags> threadSocketTags = new ThreadLocal<SocketTags>() { - @Override - protected SocketTags initialValue() { - return new SocketTags(); - } - }; - - public static void install() { - SocketTagger.set(new NetworkManagementSocketTagger()); - } - - public static int setThreadSocketStatsTag(int tag) { - final int old = threadSocketTags.get().statsTag; - threadSocketTags.get().statsTag = tag; - return old; - } - - public static int getThreadSocketStatsTag() { - return threadSocketTags.get().statsTag; - } - - public static int setThreadSocketStatsUid(int uid) { - final int old = threadSocketTags.get().statsUid; - threadSocketTags.get().statsUid = uid; - return old; - } - - public static int getThreadSocketStatsUid() { - return threadSocketTags.get().statsUid; - } - - @Override - public void tag(FileDescriptor fd) throws SocketException { - final SocketTags options = threadSocketTags.get(); - if (LOGD) { - Log.d(TAG, "tagSocket(" + fd.getInt$() + ") with statsTag=0x" - + Integer.toHexString(options.statsTag) + ", statsUid=" + options.statsUid); - } - if (options.statsTag == -1) { - StrictMode.noteUntaggedSocket(); - } - // TODO: skip tagging when options would be no-op - tagSocketFd(fd, options.statsTag, options.statsUid); - } - - private void tagSocketFd(FileDescriptor fd, int tag, int uid) { - if (tag == -1 && uid == -1) return; - - final int errno = native_tagSocketFd(fd, tag, uid); - if (errno < 0) { - Log.i(TAG, "tagSocketFd(" + fd.getInt$() + ", " - + tag + ", " - + uid + ") failed with errno" + errno); - } - } - - @Override - public void untag(FileDescriptor fd) throws SocketException { - if (LOGD) { - Log.i(TAG, "untagSocket(" + fd.getInt$() + ")"); - } - unTagSocketFd(fd); - } - - private void unTagSocketFd(FileDescriptor fd) { - final SocketTags options = threadSocketTags.get(); - if (options.statsTag == -1 && options.statsUid == -1) return; - - final int errno = native_untagSocketFd(fd); - if (errno < 0) { - Log.w(TAG, "untagSocket(" + fd.getInt$() + ") failed with errno " + errno); - } - } - - public static class SocketTags { - public int statsTag = -1; - public int statsUid = -1; - } - - /** - * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming - * format like {@code 0x7fffffff00000000}. - */ - public static int kernelToTag(String string) { - int length = string.length(); - if (length > 10) { - return Long.decode(string.substring(0, length - 8)).intValue(); - } else { - return 0; - } - } - - private static native int native_tagSocketFd(FileDescriptor fd, int tag, int uid); - private static native int native_untagSocketFd(FileDescriptor fd); -} diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java index 17f3455d20a2..668d1cba921b 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java @@ -22,8 +22,6 @@ import static android.net.NetworkStats.TAG_ALL; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; -import static com.android.server.NetworkManagementSocketTagger.kernelToTag; - import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -470,6 +468,19 @@ public class NetworkStatsFactory { } /** + * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming + * format like {@code 0x7fffffff00000000}. + */ + public static int kernelToTag(String string) { + int length = string.length(); + if (length > 10) { + return Long.decode(string.substring(0, length - 8)).intValue(); + } else { + return 0; + } + } + + /** * Parse statistics from file into given {@link NetworkStats} object. Values * are expected to monotonically increase since device boot. */ diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index 748b0ae02088..9f3371b724cf 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -20,9 +20,6 @@ import static android.Manifest.permission.NETWORK_STATS_PROVIDER; import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; import static android.Manifest.permission.UPDATE_DEVICE_STATS; import static android.app.usage.NetworkStatsManager.PREFIX_DEV; -import static android.app.usage.NetworkStatsManager.PREFIX_UID; -import static android.app.usage.NetworkStatsManager.PREFIX_UID_TAG; -import static android.app.usage.NetworkStatsManager.PREFIX_XT; import static android.content.Intent.ACTION_SHUTDOWN; import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.ACTION_USER_REMOVED; @@ -50,6 +47,9 @@ import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.MB_IN_BYTES; import static android.net.TrafficStats.UID_TETHERING; import static android.net.TrafficStats.UNSUPPORTED; +import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID; +import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG; +import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT; import static android.os.Trace.TRACE_TAG_NETWORK; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.text.format.DateUtils.DAY_IN_MILLIS; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java index 1d0ae9912d97..b65e976c4829 100755 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java @@ -37,7 +37,7 @@ import android.view.View; import android.widget.Button; import com.android.internal.app.AlertActivity; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import java.io.File; import java.io.FileInputStream; @@ -154,8 +154,8 @@ public class InstallInstalling extends AlertActivity { final PackageLite pkg = result.getResult(); params.setAppPackageName(pkg.getPackageName()); params.setInstallLocation(pkg.getInstallLocation()); - params.setSize( - PackageHelper.calculateInstalledSize(pkg, params.abiOverride)); + params.setSize(InstallLocationUtils.calculateInstalledSize(pkg, + params.abiOverride)); } } catch (IOException e) { Log.e(LOG_TAG, diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index e78923e54b07..0fe869fc44ef 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1549,6 +1549,17 @@ <!-- Content description of the no calling for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_no_calling">No calling.</string> + <!-- Screensaver overlay which displays the time. [CHAR LIMIT=20] --> + <string name="dream_complication_title_time">Time</string> + <!-- Screensaver overlay which displays the date. [CHAR LIMIT=20] --> + <string name="dream_complication_title_date">Date</string> + <!-- Screensaver overlay which displays the weather. [CHAR LIMIT=20] --> + <string name="dream_complication_title_weather">Weather</string> + <!-- Screensaver overlay which displays air quality. [CHAR LIMIT=20] --> + <string name="dream_complication_title_aqi">Air Quality</string> + <!-- Screensaver overlay which displays cast info. [CHAR LIMIT=20] --> + <string name="dream_complication_title_cast_info">Cast Info</string> + <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] --> <string name="avatar_picker_title">Choose a profile picture</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 3f322d61e036..f7b297461f15 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -16,8 +16,11 @@ package com.android.settingslib; +import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY; + import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; @@ -102,8 +105,11 @@ public class RestrictedPreferenceHelper { if (mDisabledSummary) { final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); if (summaryView != null) { - final CharSequence disabledText = summaryView.getContext().getText( - R.string.disabled_by_admin_summary_text); + final CharSequence disabledText = mContext + .getSystemService(DevicePolicyManager.class) + .getString(CONTROLLED_BY_ADMIN_SUMMARY, + () -> summaryView.getContext().getString( + R.string.disabled_by_admin_summary_text)); if (mDisabledByAdmin) { summaryView.setText(disabledText); } else if (mDisabledByAppOps) { diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index d73e45eba5dc..883e0806f5f5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -1,7 +1,10 @@ package com.android.settingslib; +import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL; + import android.annotation.ColorInt; import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -124,7 +127,8 @@ public class Utils { String name = info != null ? info.name : null; if (info.isManagedProfile()) { // We use predefined values for managed profiles - return context.getString(R.string.managed_user_title); + return context.getSystemService(DevicePolicyManager.class).getString( + WORK_PROFILE_USER_LABEL, () -> context.getString(R.string.managed_user_title)); } else if (info.isGuest()) { name = context.getString(R.string.user_guest); } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java new file mode 100644 index 000000000000..9dfc8eaac024 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppIconCacheManager.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.applications; + +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; +import android.util.Log; +import android.util.LruCache; + +/** + * Cache app icon for management. + */ +public class AppIconCacheManager { + private static final String TAG = "AppIconCacheManager"; + private static final float CACHE_RATIO = 0.1f; + private static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb(); + private static final String DELIMITER = ":"; + private static AppIconCacheManager sAppIconCacheManager; + private final LruCache<String, Drawable> mDrawableCache; + + private AppIconCacheManager() { + mDrawableCache = new LruCache<String, Drawable>(MAX_CACHE_SIZE_IN_KB) { + @Override + protected int sizeOf(String key, Drawable drawable) { + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap().getByteCount() / 1024; + } + // Rough estimate each pixel will use 4 bytes by default. + return drawable.getIntrinsicHeight() * drawable.getIntrinsicWidth() * 4 / 1024; + } + }; + } + + /** + * Get an {@link AppIconCacheManager} instance. + */ + public static synchronized AppIconCacheManager getInstance() { + if (sAppIconCacheManager == null) { + sAppIconCacheManager = new AppIconCacheManager(); + } + return sAppIconCacheManager; + } + + /** + * Put app icon to cache + * + * @param packageName of icon + * @param uid of packageName + * @param drawable app icon + */ + public void put(String packageName, int uid, Drawable drawable) { + final String key = getKey(packageName, uid); + if (key == null || drawable == null || drawable.getIntrinsicHeight() < 0 + || drawable.getIntrinsicWidth() < 0) { + Log.w(TAG, "Invalid key or drawable."); + return; + } + mDrawableCache.put(key, drawable); + } + + /** + * Get app icon from cache. + * + * @param packageName of icon + * @param uid of packageName + * @return app icon + */ + public Drawable get(String packageName, int uid) { + final String key = getKey(packageName, uid); + if (key == null) { + Log.w(TAG, "Invalid key with package or uid."); + return null; + } + final Drawable cachedDrawable = mDrawableCache.get(key); + return cachedDrawable != null ? cachedDrawable.mutate() : null; + } + + /** + * Release cache. + */ + public static void release() { + if (sAppIconCacheManager != null) { + sAppIconCacheManager.mDrawableCache.evictAll(); + } + } + + private static String getKey(String packageName, int uid) { + if (packageName == null || uid < 0) { + return null; + } + return packageName + DELIMITER + UserHandle.getUserId(uid); + } + + private static int getMaxCacheInKb() { + return Math.round(CACHE_RATIO * Runtime.getRuntime().maxMemory() / 1024); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java index a5da8b6bd15e..cc4fef8399c3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java @@ -25,6 +25,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; import android.hardware.usb.IUsbManager; import android.net.Uri; import android.os.Environment; @@ -35,7 +36,9 @@ import android.text.TextUtils; import android.util.Log; import com.android.settingslib.R; +import com.android.settingslib.Utils; import com.android.settingslib.applications.instantapps.InstantAppDataProvider; +import com.android.settingslib.utils.ThreadUtils; import java.util.ArrayList; import java.util.List; @@ -212,4 +215,82 @@ public class AppUtils { UserHandle.myUserId()); return TextUtils.equals(packageName, defaultBrowserPackage); } + + /** + * Get the app icon by app entry. + * + * @param context caller's context + * @param appEntry AppEntry of ApplicationsState + * @return app icon of the app entry + */ + public static Drawable getIcon(Context context, ApplicationsState.AppEntry appEntry) { + if (appEntry == null || appEntry.info == null) { + return null; + } + + final AppIconCacheManager appIconCacheManager = AppIconCacheManager.getInstance(); + final String packageName = appEntry.info.packageName; + final int uid = appEntry.info.uid; + + Drawable icon = appIconCacheManager.get(packageName, uid); + if (icon == null) { + if (appEntry.apkFile != null && appEntry.apkFile.exists()) { + icon = Utils.getBadgedIcon(context, appEntry.info); + appIconCacheManager.put(packageName, uid, icon); + } else { + setAppEntryMounted(appEntry, /* mounted= */ false); + icon = context.getDrawable( + com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon); + } + } else if (!appEntry.mounted && appEntry.apkFile != null && appEntry.apkFile.exists()) { + // If the app wasn't mounted but is now mounted, reload its icon. + setAppEntryMounted(appEntry, /* mounted= */ true); + icon = Utils.getBadgedIcon(context, appEntry.info); + appIconCacheManager.put(packageName, uid, icon); + } + + return icon; + } + + /** + * Get the app icon from cache by app entry. + * + * @param appEntry AppEntry of ApplicationsState + * @return app icon of the app entry + */ + public static Drawable getIconFromCache(ApplicationsState.AppEntry appEntry) { + return appEntry == null || appEntry.info == null ? null + : AppIconCacheManager.getInstance().get( + appEntry.info.packageName, + appEntry.info.uid); + } + + /** + * Preload the top N icons of app entry list. + * + * @param context caller's context + * @param appEntries AppEntry list of ApplicationsState + * @param number the number of Top N icons of the appEntries + */ + public static void preloadTopIcons(Context context, + ArrayList<ApplicationsState.AppEntry> appEntries, int number) { + if (appEntries == null || appEntries.isEmpty() || number <= 0) { + return; + } + + for (int i = 0; i < Math.min(appEntries.size(), number); i++) { + final ApplicationsState.AppEntry entry = appEntries.get(i); + ThreadUtils.postOnBackgroundThread(() -> { + getIcon(context, entry); + }); + } + } + + private static void setAppEntryMounted(ApplicationsState.AppEntry appEntry, boolean mounted) { + if (appEntry.mounted != mounted) { + synchronized (appEntry) { + appEntry.mounted = mounted; + } + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index f046f06cc691..fdb06072bbd1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -95,6 +95,7 @@ public class ApplicationsState { private static final Object sLock = new Object(); private static final Pattern REMOVE_DIACRITICALS_PATTERN = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); + private static final String SETTING_PKG = "com.android.settings"; @VisibleForTesting static ApplicationsState sInstance; @@ -492,6 +493,9 @@ public class ApplicationsState { return null; } + /** + * Starting Android T, this method will not be used if {@link AppIconCacheManager} is applied. + */ public void ensureIcon(AppEntry entry) { if (entry.icon != null) { return; @@ -758,6 +762,10 @@ public class ApplicationsState { return null; } + private static boolean isAppIconCacheEnabled(Context context) { + return SETTING_PKG.equals(context.getPackageName()); + } + void rebuildActiveSessions() { synchronized (mEntriesMap) { if (!mSessionsChanged) { @@ -806,6 +814,11 @@ public class ApplicationsState { } else { mHasLifecycle = false; } + + if (isAppIconCacheEnabled(mContext)) { + // Skip the preloading all icons step to save memory usage. + mFlags = mFlags & ~FLAG_SESSION_REQUEST_ICONS; + } } @SessionFlags @@ -814,7 +827,12 @@ public class ApplicationsState { } public void setSessionFlags(@SessionFlags int flags) { - mFlags = flags; + if (isAppIconCacheEnabled(mContext)) { + // Skip the preloading all icons step to save memory usage. + mFlags = flags & ~FLAG_SESSION_REQUEST_ICONS; + } else { + mFlags = flags; + } } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) @@ -1576,6 +1594,10 @@ public class ApplicationsState { // Need to synchronize on 'this' for the following. public ApplicationInfo info; + /** + * Starting Android T, this field will not be used if {@link AppIconCacheManager} is + * applied. + */ public Drawable icon; public String sizeStr; public String internalSizeStr; @@ -1596,15 +1618,11 @@ public class ApplicationsState { this.size = SIZE_UNKNOWN; this.sizeStale = true; ensureLabel(context); - // Speed up the cache of the icon and label description if they haven't been created. - ThreadUtils.postOnBackgroundThread(() -> { - if (this.icon == null) { - this.ensureIconLocked(context); - } - if (this.labelDescription == null) { - this.ensureLabelDescriptionLocked(context); - } - }); + // Speed up the cache of the label description if they haven't been created. + if (this.labelDescription == null) { + ThreadUtils.postOnBackgroundThread( + () -> this.ensureLabelDescriptionLocked(context)); + } } public void ensureLabel(Context context) { @@ -1620,7 +1638,15 @@ public class ApplicationsState { } } + /** + * Starting Android T, this method will not be used if {@link AppIconCacheManager} is + * applied. + */ boolean ensureIconLocked(Context context) { + if (isAppIconCacheEnabled(context)) { + return false; + } + if (this.icon == null) { if (this.apkFile.exists()) { this.icon = Utils.getBadgedIcon(context, info); diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index 46e31ceb7485..6bf43e528009 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -34,6 +34,7 @@ import android.os.ServiceManager; import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; @@ -50,6 +51,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -292,6 +294,11 @@ public class DreamBackend { } } + /** Returns whether a particular complication is enabled */ + public boolean isComplicationEnabled(@ComplicationType int complication) { + return getEnabledComplications().contains(complication); + } + /** Gets all complications which have been enabled by the user. */ public Set<Integer> getEnabledComplications() { final String enabledComplications = Settings.Secure.getString( @@ -331,6 +338,35 @@ public class DreamBackend { convertToString(enabledComplications)); } + /** + * Gets the title of a particular complication type to be displayed to the user. If there + * is no title, null is returned. + */ + @Nullable + public CharSequence getComplicationTitle(@ComplicationType int complicationType) { + int res = 0; + switch (complicationType) { + case COMPLICATION_TYPE_TIME: + res = R.string.dream_complication_title_time; + break; + case COMPLICATION_TYPE_DATE: + res = R.string.dream_complication_title_date; + break; + case COMPLICATION_TYPE_WEATHER: + res = R.string.dream_complication_title_weather; + break; + case COMPLICATION_TYPE_AIR_QUALITY: + res = R.string.dream_complication_title_aqi; + break; + case COMPLICATION_TYPE_CAST_INFO: + res = R.string.dream_complication_title_cast_info; + break; + default: + return null; + } + return mContext.getString(res); + } + private static String convertToString(Set<Integer> set) { return set.stream() .map(String::valueOf) @@ -338,6 +374,9 @@ public class DreamBackend { } private static Set<Integer> parseFromString(String string) { + if (TextUtils.isEmpty(string)) { + return new HashSet<>(); + } return Arrays.stream(string.split(",")) .map(Integer::parseInt) .collect(Collectors.toSet()); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java new file mode 100644 index 000000000000..64f8bef1ecf3 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppIconCacheManagerTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.applications; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; + +import android.graphics.drawable.Drawable; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class AppIconCacheManagerTest { + + private static final String APP_PACKAGE_NAME = "com.test.app"; + private static final int APP_UID = 9999; + + @Mock + private Drawable mIcon; + + private AppIconCacheManager mAppIconCacheManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mAppIconCacheManager = AppIconCacheManager.getInstance(); + doReturn(10).when(mIcon).getIntrinsicHeight(); + doReturn(10).when(mIcon).getIntrinsicWidth(); + doReturn(mIcon).when(mIcon).mutate(); + } + + @After + public void tearDown() { + AppIconCacheManager.release(); + } + + @Test + public void get_invalidPackageOrUid_shouldReturnNull() { + assertThat(mAppIconCacheManager.get(/* packageName= */ null, /* uid= */ -1)).isNull(); + } + + @Test + public void put_invalidPackageOrUid_shouldNotCrash() { + mAppIconCacheManager.put(/* packageName= */ null, /* uid= */ 0, mIcon); + // no crash + } + + @Test + public void put_invalidIcon_shouldNotCacheIcon() { + mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, /* drawable= */ null); + + assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull(); + } + + @Test + public void put_invalidIconSize_shouldNotCacheIcon() { + doReturn(-1).when(mIcon).getIntrinsicHeight(); + doReturn(-1).when(mIcon).getIntrinsicWidth(); + + mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon); + + assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull(); + } + + @Test + public void put_shouldCacheIcon() { + mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon); + + assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isEqualTo(mIcon); + } + + @Test + public void release_noInstance_shouldNotCrash() { + mAppIconCacheManager = null; + + AppIconCacheManager.release(); + // no crash + } + + @Test + public void release_existInstance_shouldClearCache() { + mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon); + + AppIconCacheManager.release(); + + assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull(); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java new file mode 100644 index 000000000000..8e448aa0eace --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/AppUtilsTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.applications; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.graphics.drawable.Drawable; + +import com.android.settingslib.Utils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +@RunWith(RobolectricTestRunner.class) +public class AppUtilsTest { + + private static final String APP_PACKAGE_NAME = "com.test.app"; + private static final int APP_UID = 9999; + + @Mock + private Drawable mIcon; + + private Context mContext; + private AppIconCacheManager mAppIconCacheManager; + private ApplicationInfo mAppInfo; + private ApplicationsState.AppEntry mAppEntry; + private ArrayList<ApplicationsState.AppEntry> mAppEntries; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mAppIconCacheManager = AppIconCacheManager.getInstance(); + mAppInfo = createApplicationInfo(APP_PACKAGE_NAME, APP_UID); + mAppEntry = createAppEntry(mAppInfo, /* id= */ 1); + mAppEntries = new ArrayList<>(Arrays.asList(mAppEntry)); + doReturn(mIcon).when(mIcon).mutate(); + } + + @After + public void tearDown() { + AppIconCacheManager.release(); + } + + @Test + public void getIcon_nullAppEntry_shouldReturnNull() { + assertThat(AppUtils.getIcon(mContext, /* appEntry= */ null)).isNull(); + } + + @Test + @Config(shadows = ShadowUtils.class) + public void getIcon_noCachedIcon_shouldNotReturnNull() { + assertThat(AppUtils.getIcon(mContext, mAppEntry)).isNotNull(); + } + + @Test + public void getIcon_existCachedIcon_shouldReturnCachedIcon() { + mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon); + + assertThat(AppUtils.getIcon(mContext, mAppEntry)).isEqualTo(mIcon); + } + + @Test + public void getIconFromCache_nullAppEntry_shouldReturnNull() { + assertThat(AppUtils.getIconFromCache(/* appEntry= */ null)).isNull(); + } + + @Test + public void getIconFromCache_shouldReturnCachedIcon() { + mAppIconCacheManager.put(APP_PACKAGE_NAME, APP_UID, mIcon); + + assertThat(AppUtils.getIconFromCache(mAppEntry)).isEqualTo(mIcon); + } + + @Test + public void preloadTopIcons_nullAppEntries_shouldNotCrash() { + AppUtils.preloadTopIcons(mContext, /* appEntries= */ null, /* number= */ 1); + // no crash + } + + @Test + public void preloadTopIcons_zeroPreloadIcons_shouldNotCacheIcons() { + AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 0); + + assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull(); + } + + @Test + @Config(shadows = ShadowUtils.class) + public void preloadTopIcons_shouldCheckIconFromCache() throws InterruptedException { + AppUtils.preloadTopIcons(mContext, mAppEntries, /* number= */ 1); + + TimeUnit.SECONDS.sleep(1); + assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNotNull(); + } + + private ApplicationsState.AppEntry createAppEntry(ApplicationInfo appInfo, int id) { + ApplicationsState.AppEntry appEntry = new ApplicationsState.AppEntry(mContext, appInfo, id); + appEntry.label = "label"; + appEntry.mounted = true; + final File apkFile = mock(File.class); + doReturn(true).when(apkFile).exists(); + try { + Field field = ApplicationsState.AppEntry.class.getDeclaredField("apkFile"); + field.setAccessible(true); + field.set(appEntry, apkFile); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Not able to mock apkFile: " + e); + } + return appEntry; + } + + private ApplicationInfo createApplicationInfo(String packageName, int uid) { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.sourceDir = "appPath"; + appInfo.packageName = packageName; + appInfo.uid = uid; + return appInfo; + } + + @Implements(Utils.class) + private static class ShadowUtils { + @Implementation + public static Drawable getBadgedIcon(Context context, ApplicationInfo appInfo) { + final Drawable icon = mock(Drawable.class); + doReturn(10).when(icon).getIntrinsicHeight(); + doReturn(10).when(icon).getIntrinsicWidth(); + doReturn(icon).when(icon).mutate(); + return icon; + } + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index 10ccd22eca83..1f2297ba3a0c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; import android.annotation.UserIdInt; +import android.app.Application; import android.app.ApplicationPackageManager; import android.app.usage.StorageStats; import android.app.usage.StorageStatsManager; @@ -110,6 +111,7 @@ public class ApplicationsStateRoboTest { private ApplicationsState mApplicationsState; private Session mSession; + private Application mApplication; @Mock private Callbacks mCallbacks; @@ -190,6 +192,7 @@ public class ApplicationsStateRoboTest { ShadowContextImpl shadowContext = Shadow.extract( RuntimeEnvironment.application.getBaseContext()); shadowContext.setSystemService(Context.STORAGE_STATS_SERVICE, mStorageStatsManager); + mApplication = spy(RuntimeEnvironment.application); StorageStats storageStats = new StorageStats(); storageStats.codeBytes = 10; storageStats.cacheBytes = 30; @@ -207,8 +210,7 @@ public class ApplicationsStateRoboTest { anyLong() /* flags */, anyInt() /* userId */)).thenReturn(new ParceledListSlice(infos)); ApplicationsState.sInstance = null; - mApplicationsState = - ApplicationsState.getInstance(RuntimeEnvironment.application, mPackageManagerService); + mApplicationsState = ApplicationsState.getInstance(mApplication, mPackageManagerService); mApplicationsState.clearEntries(); mSession = mApplicationsState.newSession(mCallbacks); @@ -703,6 +705,23 @@ public class ApplicationsStateRoboTest { verify(mApplicationsState, never()).clearEntries(); } + @Test + public void testDefaultSession_enabledAppIconCache_shouldSkipPreloadIcon() { + when(mApplication.getPackageName()).thenReturn("com.android.settings"); + mSession.onResume(); + + addApp(HOME_PACKAGE_NAME, 1); + addApp(LAUNCHABLE_PACKAGE_NAME, 2); + mSession.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); + processAllMessages(); + verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); + + List<AppEntry> appEntries = mAppEntriesCaptor.getValue(); + for (AppEntry appEntry : appEntries) { + assertThat(appEntry.icon).isNull(); + } + } + private void setupDoResumeIfNeededLocked(ArrayList<ApplicationInfo> ownerApps, ArrayList<ApplicationInfo> profileApps) throws RemoteException { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index dc7632dbc4a9..b851232ace82 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -46,7 +46,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import com.android.internal.telephony.Phone; import com.android.internal.telephony.RILConstants; import com.android.internal.util.XmlUtils; @@ -783,7 +783,7 @@ class DatabaseHelper extends SQLiteOpenHelper { + " VALUES(?,?);"); loadSetting(stmt, Global.SET_INSTALL_LOCATION, 0); loadSetting(stmt, Global.DEFAULT_INSTALL_LOCATION, - PackageHelper.APP_INSTALL_AUTO); + InstallLocationUtils.APP_INSTALL_AUTO); db.setTransactionSuccessful(); } finally { db.endTransaction(); @@ -2534,7 +2534,7 @@ class DatabaseHelper extends SQLiteOpenHelper { loadSetting(stmt, Settings.Global.SET_INSTALL_LOCATION, 0); loadSetting(stmt, Settings.Global.DEFAULT_INSTALL_LOCATION, - PackageHelper.APP_INSTALL_AUTO); + InstallLocationUtils.APP_INSTALL_AUTO); // Set default cdma emergency tone loadSetting(stmt, Settings.Global.EMERGENCY_TONE, 0); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 27fc6ba47ec9..ca90fbedd4e0 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -192,6 +192,9 @@ <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" /> <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" /> <uses-permission android:name="android.permission.WHITELIST_RESTRICTED_PERMISSIONS" /> + <!-- Permission required for processes that don't own the focused window to switch + touch mode state --> + <uses-permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE" /> <!-- Permission required to test onPermissionsChangedListener --> <uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" /> <uses-permission android:name="android.permission.SET_KEYBOARD_LAYOUT" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index f35f5dd0c3ae..59893817da6f 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -213,6 +213,9 @@ <!-- DevicePolicyManager get user restrictions --> <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" /> + <!-- DevicePolicyManager get admin policy --> + <uses-permission android:name="android.permission.QUERY_ADMIN_POLICY" /> + <!-- TV picture-in-picture --> <uses-permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE" /> @@ -301,6 +304,10 @@ <!-- For clipboard overlay --> <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> + <uses-permission android:name="android.permission.SET_CLIP_SOURCE" /> + + <!-- To change system language (HDMI CEC) --> + <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" /> @@ -485,6 +492,16 @@ android:excludeFromRecents="true"> </activity> + <!-- started from HdmiCecLocalDevicePlayback --> + <activity android:name=".hdmi.HdmiCecSetMenuLanguageActivity" + android:exported="true" + android:launchMode="singleTop" + android:permission="android.permission.CHANGE_CONFIGURATION" + android:theme="@style/BottomSheet" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true"> + </activity> + <!-- started from SensoryPrivacyService --> <activity android:name=".sensorprivacy.SensorUseStartedActivity" android:exported="true" diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index dee4ff5a0bf5..9722b1fe2d2e 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -1,7 +1,7 @@ { // Looking for unit test presubmit configuration? // This currently lives in ATP config apct/system_ui/unit_test - "presubmit": [ + "presubmit-large": [ { "name": "PlatformScenarioTests", "options": [ @@ -24,7 +24,9 @@ "exclude-annotation": "android.platform.test.scenario.annotation.FoldableOnly" } ] - }, + } + ], + "presubmit": [ { "name": "SystemUIGoogleTests", "options": [ diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 2a3761e4ca05..7e31909613ee 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -17,6 +17,7 @@ <com.android.systemui.clipboardoverlay.DraggableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + android:theme="@style/Screenshot" android:alpha="0" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -50,7 +51,8 @@ <LinearLayout android:id="@+id/actions" android:layout_width="wrap_content" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:animateLayoutChanges="true"> <include layout="@layout/screenshot_action_chip" android:id="@+id/remote_copy_chip"/> <include layout="@layout/screenshot_action_chip" @@ -64,7 +66,7 @@ android:layout_marginStart="@dimen/overlay_offset_x" android:layout_marginBottom="@dimen/overlay_offset_y" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background" android:elevation="@dimen/overlay_preview_elevation" app:layout_constraintEnd_toEndOf="@id/clipboard_preview_end" app:layout_constraintTop_toTopOf="@id/clipboard_preview_top" diff --git a/packages/SystemUI/res/layout/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml index 7fd029cb2c1e..11a566588738 100644 --- a/packages/SystemUI/res/layout/controls_fullscreen.xml +++ b/packages/SystemUI/res/layout/controls_fullscreen.xml @@ -35,7 +35,8 @@ android:layout_height="wrap_content" android:clipChildren="false" android:orientation="vertical" - android:clipToPadding="false" /> + android:clipToPadding="false" + android:paddingHorizontal="@dimen/controls_padding_horizontal" /> </com.android.systemui.globalactions.MinHeightScrollView> </LinearLayout> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml new file mode 100644 index 000000000000..b6f516fd2042 --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. +--> +<TextClock + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/date_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="@dimen/dream_overlay_complication_clock_date_padding_left" + android:paddingBottom="@dimen/dream_overlay_complication_clock_date_padding_bottom" + android:gravity="center_horizontal" + android:textColor="@android:color/white" + android:shadowColor="@color/keyguard_shadow_color" + android:shadowRadius="?attr/shadowRadius" + android:format12Hour="EEE, MMM d" + android:format24Hour="EEE, MMM d" + android:textSize="@dimen/dream_overlay_complication_clock_date_text_size"/> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml new file mode 100644 index 000000000000..a41d34f8ab41 --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. +--> +<TextClock + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/time_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="@dimen/dream_overlay_complication_clock_time_padding_left" + android:fontFamily="sans-serif-thin" + android:textColor="@android:color/white" + android:format12Hour="h:mm" + android:format24Hour="kk:mm" + android:shadowColor="@color/keyguard_shadow_color" + android:shadowRadius="?attr/shadowRadius" + android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml new file mode 100644 index 000000000000..08f0d6781b04 --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_complication_weather.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. +--> +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/weather_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="@dimen/dream_overlay_complication_weather_padding_left" + android:paddingBottom="@dimen/dream_overlay_complication_weather_padding_bottom" + android:textColor="@android:color/white" + android:shadowColor="@color/keyguard_shadow_color" + android:shadowRadius="?attr/shadowRadius" + android:textSize="@dimen/dream_overlay_complication_weather_text_size"/> diff --git a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml index 51359471ff98..f4eb32f7cf22 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml +++ b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml @@ -20,30 +20,28 @@ android:id="@+id/dream_overlay_complications_layer" android:layout_width="match_parent" android:layout_height="match_parent"> - <TextClock - android:id="@+id/time_view" + <androidx.constraintlayout.widget.Guideline android:layout_width="wrap_content" android:layout_height="wrap_content" - android:fontFamily="sans-serif-thin" - android:format12Hour="h:mm" - android:format24Hour="kk:mm" - android:shadowColor="#B2000000" - android:shadowRadius="2.0" - android:singleLine="true" - android:textSize="72sp" - app:layout_constraintBottom_toTopOf="@+id/date_view" - app:layout_constraintStart_toStartOf="parent" /> - <TextClock - android:id="@+id/date_view" + android:id="@+id/complication_top_guide" + app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_top_percent" + android:orientation="horizontal"/> + <androidx.constraintlayout.widget.Guideline android:layout_width="wrap_content" android:layout_height="wrap_content" - android:shadowColor="#B2000000" - android:shadowRadius="2.0" - android:format12Hour="EEE, MMM d" - android:format24Hour="EEE, MMM d" - android:singleLine="true" - android:textSize="18sp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="@+id/time_view" - app:layout_constraintStart_toStartOf="@+id/time_view" /> + android:id="@+id/complication_end_guide" + app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_end_percent" + android:orientation="vertical"/> + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/complication_bottom_guide" + app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_bottom_percent" + android:orientation="horizontal"/> + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/complication_start_guide" + app:layout_constraintGuide_percent="@dimen/dream_overlay_complication_guide_start_percent" + android:orientation="vertical"/> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml index 4929f502fef0..3c2183d0ca61 100644 --- a/packages/SystemUI/res/layout/dream_overlay_container.xml +++ b/packages/SystemUI/res/layout/dream_overlay_container.xml @@ -25,7 +25,7 @@ android:id="@+id/dream_overlay_content" android:layout_width="match_parent" android:layout_height="0dp" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toBottomOf="@id/dream_overlay_status_bar" app:layout_constraintBottom_toBottomOf="parent" /> <com.android.systemui.dreams.DreamOverlayStatusBarView diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 2290964eccd0..39d7f4f5db4e 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -122,7 +122,7 @@ android:layout_marginTop="@dimen/notification_panel_margin_top" android:layout_width="@dimen/notification_panel_width" android:layout_height="match_parent" - android:layout_marginBottom="@dimen/close_handle_underlap" + android:layout_marginBottom="@dimen/notification_panel_margin_bottom" android:importantForAccessibility="no" systemui:layout_constraintStart_toStartOf="parent" systemui:layout_constraintEnd_toEndOf="parent" diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index ac4dfd212bb8..8c5006de577e 100644 --- a/packages/SystemUI/res/values-land/config.xml +++ b/packages/SystemUI/res/values-land/config.xml @@ -31,9 +31,6 @@ <!-- orientation of the dead zone when touches have recently occurred elsewhere on screen --> <integer name="navigation_bar_deadzone_orientation">1</integer> - <!-- Max number of columns for quick controls area --> - <integer name="controls_max_columns">4</integer> - <!-- Max number of columns for power menu --> <integer name="power_menu_max_columns">4</integer> </resources> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index fc5edf3ade8f..9d24e9b97da3 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -66,4 +66,6 @@ <dimen name="controls_management_favorites_top_margin">8dp</dimen> <dimen name="wallet_card_carousel_container_top_margin">24dp</dimen> + + <dimen name="large_dialog_width">348dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index dabc3108458f..fe546f65bb13 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -15,9 +15,6 @@ ~ limitations under the License --> <resources> - <!-- Max number of columns for quick controls area --> - <integer name="controls_max_columns">2</integer> - <!-- The maximum number of rows in the QSPanel --> <integer name="quick_settings_max_rows">3</integer> diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml index 89d046b83d97..c2cec5231243 100644 --- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -26,6 +26,10 @@ keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp --> <dimen name="keyguard_clock_top_margin">8dp</dimen> + <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen> + + <dimen name="notification_panel_margin_bottom">48dp</dimen> + <!-- Limit the TaskView to this percentage of the overall screen width (0.0 - 1.0) --> <item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">0.45</item> <dimen name="controls_task_view_right_margin">8dp</dimen> diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml index 02fd25bd077c..3c6a81e7c617 100644 --- a/packages/SystemUI/res/values-sw600dp-port/config.xml +++ b/packages/SystemUI/res/values-sw600dp-port/config.xml @@ -15,7 +15,6 @@ ~ limitations under the License --> <resources> - <!-- The maximum number of tiles in the QuickQSPanel --> <integer name="quick_qs_panel_max_tiles">6</integer> diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index f5dc7e3e16da..1b8453ae824d 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -29,9 +29,6 @@ <!-- orientation of the dead zone when touches have recently occurred elsewhere on screen --> <integer name="navigation_bar_deadzone_orientation">0</integer> - <!-- Max number of columns for quick controls area --> - <integer name="controls_max_columns">4</integer> - <!-- How many lines to show in the security footer --> <integer name="qs_security_footer_maxLines">1</integer> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 7d033018c27f..a66ed15c9d84 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -69,5 +69,5 @@ <dimen name="qs_detail_margin_top">0dp</dimen> <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) --> - <dimen name="large_dialog_width">504dp</dimen> + <dimen name="large_dialog_width">472dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml index ae89ef4ccc86..be34a48d1cd6 100644 --- a/packages/SystemUI/res/values-sw720dp-land/config.xml +++ b/packages/SystemUI/res/values-sw720dp-land/config.xml @@ -15,9 +15,6 @@ ~ limitations under the License --> <resources> - <!-- Max number of columns for quick controls area --> - <integer name="controls_max_columns">2</integer> - <!-- The maximum number of rows in the QSPanel --> <integer name="quick_settings_max_rows">3</integer> diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml new file mode 100644 index 000000000000..219fd43c0e83 --- /dev/null +++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (c) 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +--> +<resources> + <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen> + <dimen name="notification_panel_margin_bottom">56dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values-w500dp/config.xml b/packages/SystemUI/res/values-w500dp/config.xml new file mode 100644 index 000000000000..ef499ff5cdb7 --- /dev/null +++ b/packages/SystemUI/res/values-w500dp/config.xml @@ -0,0 +1,20 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <!-- Max number of columns for quick controls area --> + <integer name="controls_max_columns">3</integer> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-w500dp/dimens.xml b/packages/SystemUI/res/values-w500dp/dimens.xml new file mode 100644 index 000000000000..5ce5ceee6dc9 --- /dev/null +++ b/packages/SystemUI/res/values-w500dp/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <dimen name="controls_padding_horizontal">75dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values-w850dp/config.xml b/packages/SystemUI/res/values-w850dp/config.xml new file mode 100644 index 000000000000..337ebe19c748 --- /dev/null +++ b/packages/SystemUI/res/values-w850dp/config.xml @@ -0,0 +1,20 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <!-- Max number of columns for quick controls area --> + <integer name="controls_max_columns">4</integer> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-w850dp/dimens.xml b/packages/SystemUI/res/values-w850dp/dimens.xml new file mode 100644 index 000000000000..bb6ba8fb07b6 --- /dev/null +++ b/packages/SystemUI/res/values-w850dp/dimens.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources> + <dimen name="controls_padding_horizontal">205dp</dimen> +</resources> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index db699242a061..de136de9dd5f 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -85,7 +85,7 @@ Contract: Pixel with fillColor blended over backgroundColor blended over translucent should equal to singleToneColor blended over translucent. --> <declare-styleable name="TonedIcon"> - <attr name="backgroundColor" format="integer" /> + <attr name="iconBackgroundColor" format="integer" /> <attr name="fillColor" format="integer" /> <attr name="singleToneColor" format="integer" /> <attr name="homeHandleColor" format="integer" /> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index b35571c17db7..bba616fe24a2 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -311,9 +311,6 @@ <!-- Move the back button drawable for 3 button layout upwards in ime mode and in portrait --> <dimen name="navbar_back_button_ime_offset">2dp</dimen> - <!-- Amount of close_handle that will NOT overlap the notification list --> - <dimen name="close_handle_underlap">32dp</dimen> - <!-- Height of the status bar header bar in the car setting. --> <dimen name="car_status_bar_header_height">128dp</dimen> @@ -376,8 +373,12 @@ --> <dimen name="nssl_split_shade_min_content_height">256dp</dimen> - <!-- The bottom margin of the panel that holds the list of notifications. --> - <dimen name="notification_panel_margin_bottom">0dp</dimen> + <dimen name="notification_panel_margin_bottom">32dp</dimen> + + <!-- The bottom padding of the panel that holds the list of notifications. --> + <dimen name="notification_panel_padding_bottom">0dp</dimen> + + <dimen name="split_shade_notifications_scrim_margin_bottom">0dp</dimen> <dimen name="notification_panel_width">@dimen/match_parent</dimen> @@ -1035,6 +1036,7 @@ <dimen name="controls_header_bottom_margin">24dp</dimen> <dimen name="controls_header_app_icon_size">24dp</dimen> <dimen name="controls_top_margin">48dp</dimen> + <dimen name="controls_padding_horizontal">0dp</dimen> <dimen name="control_header_text_size">20sp</dimen> <dimen name="control_item_text_size">16sp</dimen> <dimen name="control_menu_item_text_size">16sp</dimen> @@ -1343,4 +1345,47 @@ <!-- Height of the area at the top of the dream overlay to allow dragging down the notifications shade. --> <dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen> + + <!-- Dream overlay complications related dimensions --> + <dimen name="dream_overlay_complication_clock_time_padding_left">50dp</dimen> + <dimen name="dream_overlay_complication_clock_time_text_size">72sp</dimen> + <dimen name="dream_overlay_complication_clock_date_padding_left">60dp</dimen> + <dimen name="dream_overlay_complication_clock_date_padding_bottom">50dp</dimen> + <dimen name="dream_overlay_complication_clock_date_text_size">18sp</dimen> + <dimen name="dream_overlay_complication_weather_padding_left">20dp</dimen> + <dimen name="dream_overlay_complication_weather_padding_bottom">50dp</dimen> + <dimen name="dream_overlay_complication_weather_text_size">18sp</dimen> + + <!-- The position of the end guide, which dream overlay complications can align their start with + if their end is aligned with the parent end. Represented as the percentage over from the + start of the parent container. --> + <item name="dream_overlay_complication_guide_end_percent" format="float" type="dimen"> + 0.75 + </item> + + <!-- The position of the start guide, which dream overlay complications can align their end to + if their start is aligned with the parent start. Represented as the percentage over from + the start of the parent container. --> + <item name="dream_overlay_complication_guide_start_percent" format="float" type="dimen"> + 0.25 + </item> + + <!-- The position of the bottom guide, which dream overlay complications can align their top to + if their bottom is aligned with the parent bottom. Represented as the percentage over from + the top of the parent container. --> + <item name="dream_overlay_complication_guide_bottom_percent" format="float" type="dimen"> + 0.90 + </item> + + <!-- The position of the top guide, which dream overlay complications can align their bottom to + if their top is aligned with the parent top. Represented as the percentage over from + the top of the parent container. --> + <item name="dream_overlay_complication_guide_top_percent" format="float" type="dimen"> + 0.10 + </item> + + <!-- The percentage of the screen from which a swipe can start to reveal the bouncer. --> + <item name="dream_overlay_bouncer_start_region_screen_percentage" format="float" type="dimen"> + .2 + </item> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 75ae52c6e6f4..4dca0b0076d9 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -123,6 +123,18 @@ <!-- Message of notification shown when trying to enable USB debugging but a secondary user is the current foreground user. --> <string name="usb_debugging_secondary_user_message">The user currently signed in to this device can\'t turn on USB debugging. To use this feature, switch to the primary user.</string> + <!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=80] --> + <string name="hdmi_cec_set_menu_language_title">Do you want to change the system language to <xliff:g id="language" example="German">%1$s</xliff:g>?</string> + + <!-- Description for the <Set Menu Language> confirmation dialog [CHAR LIMIT=NONE] --> + <string name="hdmi_cec_set_menu_language_description">System language change requested by another device</string> + + <!-- Button label for accepting language change [CHAR LIMIT=25] --> + <string name="hdmi_cec_set_menu_language_accept">Change language</string> + + <!-- Button label for declining language change [CHAR LIMIT=25] --> + <string name="hdmi_cec_set_menu_language_decline">Keep current language</string> + <!-- Title of confirmation dialog for wireless debugging [CHAR LIMIT=NONE] --> <string name="wifi_debugging_title">Allow wireless debugging on this network?</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index ac9873938a29..57f1f3f1606c 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -404,19 +404,19 @@ </style> <style name="DualToneLightTheme"> - <item name="backgroundColor">@color/light_mode_icon_color_dual_tone_background</item> + <item name="iconBackgroundColor">@color/light_mode_icon_color_dual_tone_background</item> <item name="fillColor">@color/light_mode_icon_color_dual_tone_fill</item> <item name="singleToneColor">@color/light_mode_icon_color_single_tone</item> <item name="homeHandleColor">@color/navigation_bar_home_handle_light_color</item> </style> <style name="DualToneDarkTheme"> - <item name="backgroundColor">@color/dark_mode_icon_color_dual_tone_background</item> + <item name="iconBackgroundColor">@color/dark_mode_icon_color_dual_tone_background</item> <item name="fillColor">@color/dark_mode_icon_color_dual_tone_fill</item> <item name="singleToneColor">@color/dark_mode_icon_color_single_tone</item> <item name="homeHandleColor">@color/navigation_bar_home_handle_dark_color</item> </style> <style name="QSHeaderDarkTheme"> - <item name="backgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item> + <item name="iconBackgroundColor">@color/dark_mode_qs_icon_color_dual_tone_background</item> <item name="fillColor">@color/dark_mode_qs_icon_color_dual_tone_fill</item> <item name="singleToneColor">@color/dark_mode_qs_icon_color_single_tone</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt new file mode 100644 index 000000000000..ffab3cd79d7f --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.animation + +import android.view.View +import android.view.ViewGroup +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import java.lang.ref.WeakReference + +/** + * Translates items away/towards the hinge when the device is opened/closed, according to the + * direction specified in [ViewIdToTranslate.direction], for a maximum of [translationMax] when + * progresses are 0. + */ +class UnfoldConstantTranslateAnimator( + private val viewsIdToTranslate: Set<ViewIdToTranslate>, + private val progressProvider: UnfoldTransitionProgressProvider +) : TransitionProgressListener { + + private var viewsToTranslate = listOf<ViewToTranslate>() + private lateinit var rootView: ViewGroup + private var translationMax = 0f + + fun init(rootView: ViewGroup, translationMax: Float) { + this.rootView = rootView + this.translationMax = translationMax + progressProvider.addCallback(this) + } + + override fun onTransitionStarted() { + registerViewsForAnimation(rootView, viewsIdToTranslate) + } + + override fun onTransitionProgress(progress: Float) { + translateViews(progress) + } + + override fun onTransitionFinished() { + translateViews(progress = 1f) + } + + private fun translateViews(progress: Float) { + // progress == 0 -> -translationMax + // progress == 1 -> 0 + val xTrans = (progress - 1f) * translationMax + viewsToTranslate.forEach { (view, direction, shouldBeAnimated) -> + if (shouldBeAnimated()) { + view.get()?.translationX = xTrans * direction.multiplier + } + } + } + + /** Finds in [parent] all views specified by [ids] and register them for the animation. */ + private fun registerViewsForAnimation(parent: ViewGroup, ids: Set<ViewIdToTranslate>) { + viewsToTranslate = + ids.mapNotNull { (id, dir, pred) -> + parent.findViewById<View>(id)?.let { view -> + ViewToTranslate(WeakReference(view), dir, pred) + } + } + } + + /** Represents a view to animate. [rootView] should contain a view with [viewId] inside. */ + data class ViewIdToTranslate( + val viewId: Int, + val direction: Direction, + val shouldBeAnimated: () -> Boolean = { true } + ) + + private data class ViewToTranslate( + val view: WeakReference<View>, + val direction: Direction, + val shouldBeAnimated: () -> Boolean + ) + + /** Direction of the animation. */ + enum class Direction(val multiplier: Float) { + LEFT(-1f), + RIGHT(1f), + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt index 9010d5154156..fc6bb500562e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt @@ -42,7 +42,9 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( * are different than actual bounds (e.g. view container may * have larger width than width of the items in the container) */ - private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {} + private val viewCenterProvider: ViewCenterProvider = object : ViewCenterProvider {}, + /** Allows to set the alpha based on the progress. */ + private val alphaProvider: AlphaProvider? = null ) : UnfoldTransitionProgressProvider.TransitionProgressListener { private val screenSize = Point() @@ -99,17 +101,27 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( override fun onTransitionProgress(progress: Float) { animatedViews.forEach { - it.view.get()?.let { view -> - translationApplier.apply( - view = view, - x = it.startTranslationX * (1 - progress), - y = it.startTranslationY * (1 - progress) - ) - } + it.applyTransition(progress) + it.applyAlpha(progress) } lastAnimationProgress = progress } + private fun AnimatedView.applyTransition(progress: Float) { + view.get()?.let { view -> + translationApplier.apply( + view = view, + x = startTranslationX * (1 - progress), + y = startTranslationY * (1 - progress) + ) + } + } + + private fun AnimatedView.applyAlpha(progress: Float) { + if (alphaProvider == null) return + view.get()?.alpha = alphaProvider.getAlpha(progress) + } + private fun createAnimatedView(view: View): AnimatedView = AnimatedView(view = WeakReference(view)).updateAnimatedView(view) @@ -146,6 +158,13 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( } } + /** Allows to set a custom alpha based on the progress. */ + interface AlphaProvider { + + /** Returns the alpha views should have at a given progress. */ + fun getAlpha(progress: Float): Float + } + /** * Interface that allows to use custom logic to get the center of the view */ diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl deleted file mode 100644 index b76be4fc719a..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalHost.aidl +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.communal; - -import com.android.systemui.shared.communal.ICommunalSource; - -/** -* An interface, implemented by SystemUI, for hosting a shared, communal surface on the lock -* screen. Clients declare themselves sources (as defined by ICommunalSource). ICommunalHost is -* meant only for the input of said sources. The lifetime scope and interactions that follow after -* are bound to source. -*/ -oneway interface ICommunalHost { - /** - * Invoked to specify the CommunalSource that should be consulted for communal surfaces to be - * displayed. - */ - void setSource(in ICommunalSource source) = 1; -}
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl deleted file mode 100644 index 7ef403b414be..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSource.aidl +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.communal; - -import com.android.systemui.shared.communal.ICommunalSurfaceCallback; - -/** - * An interface, implemented by clients of CommunalHost, to provide communal surfaces for SystemUI. - * The associated binder proxy will be retained by SystemUI and called on-demand when a communal - * surface is needed (either new instantiation or update). - */ -oneway interface ICommunalSource { - /** - * Called by the CommunalHost when a new communal surface is needed. The provided arguments - * match the arguments necessary to construct a SurfaceControlViewHost for producing a - * SurfacePackage to return. - */ - void getCommunalSurface(in IBinder hostToken, in int width, in int height, in int displayId, - in ICommunalSurfaceCallback callback) = 1; -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt index cb25e1a2a40e..89d6fb5f062f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt @@ -17,11 +17,13 @@ package com.android.keyguard import android.content.Context -import android.view.View import android.view.ViewGroup import com.android.systemui.R +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate import com.android.systemui.unfold.SysUIUnfoldScope -import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import javax.inject.Inject @@ -30,84 +32,37 @@ import javax.inject.Inject * the set of ids, which also dictact which direction to move and when, via a filter function. */ @SysUIUnfoldScope -class KeyguardUnfoldTransition @Inject constructor( - val context: Context, - val unfoldProgressProvider: NaturalRotationUnfoldProgressProvider +class KeyguardUnfoldTransition +@Inject +constructor( + private val context: Context, + unfoldProgressProvider: NaturalRotationUnfoldProgressProvider ) { - companion object { - final val LEFT = -1 - final val RIGHT = 1 - } + /** Certain views only need to move if they are not currently centered */ + var statusViewCentered = false private val filterSplitShadeOnly = { !statusViewCentered } private val filterNever = { true } - private val ids = setOf( - Triple(R.id.keyguard_status_area, LEFT, filterNever), - Triple(R.id.controls_button, LEFT, filterNever), - Triple(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly), - Triple(R.id.lockscreen_clock_view, LEFT, filterNever), - Triple(R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly), - Triple(R.id.wallet_button, RIGHT, filterNever) - ) - private var parent: ViewGroup? = null - private var views = listOf<Triple<View, Int, () -> Boolean>>() - private var xTranslationMax = 0f - - /** - * Certain views only need to move if they are not currently centered - */ - var statusViewCentered = false - - init { - unfoldProgressProvider.addCallback( - object : TransitionProgressListener { - override fun onTransitionStarted() { - findViews() - } - - override fun onTransitionProgress(progress: Float) { - translateViews(progress) - } - - override fun onTransitionFinished() { - translateViews(1f) - } - } - ) + private val translateAnimator by lazy { + UnfoldConstantTranslateAnimator( + viewsIdToTranslate = + setOf( + ViewIdToTranslate(R.id.keyguard_status_area, LEFT, filterNever), + ViewIdToTranslate(R.id.controls_button, LEFT, filterNever), + ViewIdToTranslate(R.id.lockscreen_clock_view_large, LEFT, filterSplitShadeOnly), + ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever), + ViewIdToTranslate( + R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly), + ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever)), + progressProvider = unfoldProgressProvider) } - /** - * Relies on the [parent] to locate views to translate - */ + /** Relies on the [parent] to locate views to translate. */ fun setup(parent: ViewGroup) { - this.parent = parent - xTranslationMax = context.resources.getDimensionPixelSize( - R.dimen.keyguard_unfold_translation_x).toFloat() - } - - /** - * Manually translate views based on set direction. At the moment - * [UnfoldMoveFromCenterAnimator] exists but moves all views a dynamic distance - * from their mid-point. This code instead will only ever translate by a fixed amount. - */ - private fun translateViews(progress: Float) { - val xTrans = progress * xTranslationMax - xTranslationMax - views.forEach { - (view, direction, pred) -> if (pred()) { - view.setTranslationX(xTrans * direction) - } - } - } - - private fun findViews() { - parent?.let { p -> - views = ids.mapNotNull { - (id, direction, pred) -> p.findViewById<View>(id)?.let { - Triple(it, direction, pred) - } - } - } + val translationMax = + context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat() + translateAnimator.init(parent, translationMax) } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f2d0427c39d3..cc10b02a8bb6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1196,6 +1196,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return fingerprintAllowed || faceAllowed; } + /** + * Returns whether the user is unlocked with a biometric that is currently bypassing + * the lock screen. + */ + public boolean getUserUnlockedWithBiometricAndIsBypassing(int userId) { + BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId); + BiometricAuthenticated face = mUserFaceAuthenticated.get(userId); + // fingerprint always bypasses + boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated + && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric); + boolean faceAllowed = face != null && face.mAuthenticated + && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric); + return fingerprintAllowed || faceAllowed && mKeyguardBypassController.canBypass(); + } + public boolean getUserTrustIsManaged(int userId) { return mUserTrustIsManaged.get(userId) && !isTrustDisabled(userId); } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 6626f59aae8c..80a3a0ebb250 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -35,7 +35,6 @@ import android.hardware.biometrics.SensorLocationInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Process; import android.os.VibrationAttributes; -import android.os.Vibrator; import android.util.DisplayMetrics; import android.util.Log; import android.util.MathUtils; @@ -60,6 +59,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -103,7 +103,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private CharSequence mUnlockedLabel; @NonNull private CharSequence mLockedLabel; - @Nullable private final Vibrator mVibrator; + @NonNull private final VibratorHelper mVibrator; @Nullable private final AuthRippleController mAuthRippleController; // Tracks the velocity of a touch to help filter out the touches that move too fast. @@ -154,7 +154,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull AccessibilityManager accessibilityManager, @NonNull ConfigurationController configurationController, @NonNull @Main DelayableExecutor executor, - @Nullable Vibrator vibrator, + @NonNull VibratorHelper vibrator, @Nullable AuthRippleController authRippleController, @NonNull @Main Resources resources ) { @@ -560,7 +560,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme switch(event.getActionMasked()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_HOVER_ENTER: - if (mVibrator != null && !mDownDetected) { + if (!mDownDetected) { mVibrator.vibrate( Process.myUid(), getContext().getOpPackageName(), @@ -647,15 +647,13 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mOnGestureDetectedRunnable.run(); } - if (mVibrator != null) { - // play device entry haptic (same as biometric success haptic) - mVibrator.vibrate( - Process.myUid(), - getContext().getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "lock-icon-device-entry", - TOUCH_VIBRATION_ATTRIBUTES); - } + // play device entry haptic (same as biometric success haptic) + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "lock-icon-device-entry", + TOUCH_VIBRATION_ATTRIBUTES); mKeyguardViewController.showBouncer(/* scrim */ true); } @@ -670,12 +668,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mVelocityTracker.recycle(); mVelocityTracker = null; } - if (mVibrator != null) { - mVibrator.cancel(); - } + mVibrator.cancel(); } - private boolean inLockIconArea(MotionEvent event) { return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) && mView.getVisibility() == View.VISIBLE; diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 08ed24caec44..b32c2b639f16 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -49,6 +49,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentService; +import com.android.systemui.hdmi.HdmiCecSetMenuLanguageHelper; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.media.dialog.MediaOutputDialogFactory; @@ -250,6 +251,7 @@ public class Dependency { @Inject Lazy<LocationController> mLocationController; @Inject Lazy<RotationLockController> mRotationLockController; @Inject Lazy<ZenModeController> mZenModeController; + @Inject Lazy<HdmiCecSetMenuLanguageHelper> mHdmiCecSetMenuLanguageHelper; @Inject Lazy<HotspotController> mHotspotController; @Inject Lazy<CastController> mCastController; @Inject Lazy<FlashlightController> mFlashlightController; diff --git a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt index fdc3229ab8d7..2b8d3e046ffa 100644 --- a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt @@ -54,11 +54,11 @@ class DualToneHandler(context: Context) { Utils.getThemeAttr(context, R.attr.lightIconTheme)) darkColor = Color( Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor), - Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.backgroundColor), + Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor), Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor)) lightColor = Color( Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor), - Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.backgroundColor), + Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor), Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor)) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index b0f7e55112af..fe5e36ef23e6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -37,6 +37,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; @@ -64,6 +65,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.concurrency.Execution; @@ -96,6 +98,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba private final Handler mHandler; private final Execution mExecution; private final CommandQueue mCommandQueue; + private final StatusBarStateController mStatusBarStateController; private final ActivityTaskManager mActivityTaskManager; @Nullable private final FingerprintManager mFingerprintManager; @@ -118,6 +121,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Nullable private UdfpsController mUdfpsController; @Nullable private IUdfpsHbmListener mUdfpsHbmListener; @Nullable private SidefpsController mSidefpsController; + @Nullable private IBiometricContextListener mBiometricContextListener; @VisibleForTesting TaskStackListener mTaskStackListener; @VisibleForTesting @@ -130,7 +134,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; - private SensorPrivacyManager mSensorPrivacyManager; + @NonNull private final SensorPrivacyManager mSensorPrivacyManager; private final WakefulnessLifecycle mWakefulnessLifecycle; private class BiometricTaskStackListener extends TaskStackListener { @@ -491,6 +495,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba Provider<SidefpsController> sidefpsControllerFactory, @NonNull DisplayManager displayManager, WakefulnessLifecycle wakefulnessLifecycle, + @NonNull StatusBarStateController statusBarStateController, @Main Handler handler) { super(context); mExecution = execution; @@ -504,6 +509,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mSidefpsControllerFactory = sidefpsControllerFactory; mWindowManager = windowManager; mUdfpsEnrolledForUser = new SparseBooleanArray(); + mOrientationListener = new BiometricDisplayListener( context, displayManager, @@ -514,6 +520,14 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba return Unit.INSTANCE; }); + mStatusBarStateController = statusBarStateController; + mStatusBarStateController.addCallback(new StatusBarStateController.StateListener() { + @Override + public void onDozingChanged(boolean isDozing) { + notifyDozeChanged(isDozing); + } + }); + mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null; int[] faceAuthLocation = context.getResources().getIntArray( @@ -564,6 +578,22 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mActivityTaskManager.registerTaskStackListener(mTaskStackListener); } + @Override + public void setBiometicContextListener(IBiometricContextListener listener) { + mBiometricContextListener = listener; + notifyDozeChanged(mStatusBarStateController.isDozing()); + } + + private void notifyDozeChanged(boolean isDozing) { + if (mBiometricContextListener != null) { + try { + mBiometricContextListener.onDozeChanged(isDozing); + } catch (RemoteException e) { + Log.w(TAG, "failed to notify initial doze state"); + } + } + } + /** * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}. * diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 5ddfd7554ac9..8052c2071d86 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -42,7 +42,6 @@ import android.os.Process; import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; -import android.os.Vibrator; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -64,6 +63,7 @@ import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.SystemUIDialogManager; @@ -117,7 +117,7 @@ public class UdfpsController implements DozeReceiver { @NonNull private final DumpManager mDumpManager; @NonNull private final SystemUIDialogManager mDialogManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Nullable private final Vibrator mVibrator; + @NonNull private final VibratorHelper mVibrator; @NonNull private final FalsingManager mFalsingManager; @NonNull private final PowerManager mPowerManager; @NonNull private final AccessibilityManager mAccessibilityManager; @@ -506,7 +506,7 @@ public class UdfpsController implements DozeReceiver { @NonNull AccessibilityManager accessibilityManager, @NonNull LockscreenShadeTransitionController lockscreenShadeTransitionController, @NonNull ScreenLifecycle screenLifecycle, - @Nullable Vibrator vibrator, + @NonNull VibratorHelper vibrator, @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator, @NonNull Optional<UdfpsHbmProvider> hbmProvider, @NonNull KeyguardStateController keyguardStateController, @@ -577,14 +577,12 @@ public class UdfpsController implements DozeReceiver { */ @VisibleForTesting public void playStartHaptic() { - if (mVibrator != null) { - mVibrator.vibrate( - Process.myUid(), - mContext.getOpPackageName(), - EFFECT_CLICK, - "udfps-onStart-click", - VIBRATION_ATTRIBUTES); - } + mVibrator.vibrate( + Process.myUid(), + mContext.getOpPackageName(), + EFFECT_CLICK, + "udfps-onStart-click", + VIBRATION_ATTRIBUTES); } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt index e23131069eab..eaee19aa5dca 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt @@ -18,16 +18,12 @@ package com.android.systemui.biometrics import android.media.AudioAttributes import android.os.VibrationEffect -import android.os.Vibrator - import com.android.keyguard.KeyguardUpdateMonitor - import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry - import java.io.PrintWriter - import javax.inject.Inject /** @@ -36,7 +32,7 @@ import javax.inject.Inject @SysUISingleton class UdfpsHapticsSimulator @Inject constructor( commandRegistry: CommandRegistry, - val vibrator: Vibrator?, + val vibrator: VibratorHelper, val keyguardUpdateMonitor: KeyguardUpdateMonitor ) : Command { val sonificationEffects = @@ -60,13 +56,13 @@ class UdfpsHapticsSimulator @Inject constructor( } "success" -> { // needs to be kept up to date with AcquisitionClient#SUCCESS_VIBRATION_EFFECT - vibrator?.vibrate( + vibrator.vibrate( VibrationEffect.get(VibrationEffect.EFFECT_CLICK), sonificationEffects) } "error" -> { // needs to be kept up to date with AcquisitionClient#ERROR_VIBRATION_EFFECT - vibrator?.vibrate( + vibrator.vibrate( VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), sonificationEffects) } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index 0e1cd51ce42c..72b40d42b7b8 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -63,7 +63,8 @@ public class ClipboardListener extends CoreStartable mClipboardOverlayController = new ClipboardOverlayController(mContext, new TimeoutHandler(mContext)); } - mClipboardOverlayController.setClipData(mClipboardManager.getPrimaryClip()); + mClipboardOverlayController.setClipData( + mClipboardManager.getPrimaryClip(), mClipboardManager.getPrimaryClipSource()); mClipboardOverlayController.setOnSessionCompleteListener(() -> { // Session is complete, free memory until it's needed again. mClipboardOverlayController = null; diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index b6bcb871ff18..12759f489a26 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -182,6 +182,7 @@ public class ClipboardOverlayController { withWindowAttached(() -> { mWindow.setContentView(mView); updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets()); + mView.requestLayout(); mView.post(this::animateIn); }); @@ -213,7 +214,7 @@ public class ClipboardOverlayController { mContext.sendBroadcast(new Intent(COPY_OVERLAY_ACTION), SELF_PERMISSION); } - void setClipData(ClipData clipData) { + void setClipData(ClipData clipData, String clipSource) { reset(); if (clipData == null || clipData.getItemCount() == 0) { showTextPreview(mContext.getResources().getString( @@ -221,7 +222,7 @@ public class ClipboardOverlayController { } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) { ClipData.Item item = clipData.getItemAt(0); if (item.getTextLinks() != null) { - AsyncTask.execute(() -> classifyText(clipData.getItemAt(0))); + AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource)); } showEditableText(item.getText()); } else if (clipData.getItemAt(0).getUri() != null) { @@ -238,7 +239,7 @@ public class ClipboardOverlayController { mOnSessionCompleteListener = runnable; } - private void classifyText(ClipData.Item item) { + private void classifyText(ClipData.Item item, String source) { ArrayList<RemoteAction> actions = new ArrayList<>(); for (TextLinks.TextLink link : item.getTextLinks().getLinks()) { TextClassification classification = mTextClassifier.classifyText( @@ -246,14 +247,14 @@ public class ClipboardOverlayController { actions.addAll(classification.getActions()); } mView.post(() -> { - for (ScreenshotActionChip chip : mActionChips) { - mActionContainer.removeView(chip); - } - mActionChips.clear(); + resetActionChips(); for (RemoteAction action : actions) { - ScreenshotActionChip chip = constructActionChip(action); - mActionContainer.addView(chip); - mActionChips.add(chip); + Intent targetIntent = action.getActionIntent().getIntent(); + if (!TextUtils.equals(source, targetIntent.getComponent().getPackageName())) { + ScreenshotActionChip chip = constructActionChip(action); + mActionContainer.addView(chip); + mActionChips.add(chip); + } } }); } @@ -451,13 +452,17 @@ public class ClipboardOverlayController { } } - private void reset() { - mView.setTranslationX(0); - mView.setAlpha(0); + private void resetActionChips() { for (ScreenshotActionChip chip : mActionChips) { mActionContainer.removeView(chip); } mActionChips.clear(); + } + + private void reset() { + mView.setTranslationX(0); + mView.setAlpha(0); + resetActionChips(); mTimeoutHandler.cancelTimeout(); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index a29f3e91f227..f87fa96dea65 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -24,7 +24,6 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.os.VibrationEffect -import android.os.Vibrator import android.service.controls.Control import android.service.controls.actions.BooleanAction import android.service.controls.actions.CommandAction @@ -32,16 +31,14 @@ import android.service.controls.actions.FloatAction import android.util.Log import android.view.HapticFeedbackConstants import com.android.internal.annotations.VisibleForTesting -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.ControlsMetricsLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.wm.shell.TaskViewFactory -import dagger.Lazy import java.util.Optional import javax.inject.Inject @@ -52,14 +49,11 @@ class ControlActionCoordinatorImpl @Inject constructor( @Main private val uiExecutor: DelayableExecutor, private val activityStarter: ActivityStarter, private val keyguardStateController: KeyguardStateController, - private val globalActionsComponent: GlobalActionsComponent, private val taskViewFactory: Optional<TaskViewFactory>, - private val broadcastDispatcher: BroadcastDispatcher, - private val lazyUiController: Lazy<ControlsUiController>, - private val controlsMetricsLogger: ControlsMetricsLogger + private val controlsMetricsLogger: ControlsMetricsLogger, + private val vibrator: VibratorHelper ) : ControlActionCoordinator { private var dialog: Dialog? = null - private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator private var pendingAction: Action? = null private var actionsInProgress = mutableSetOf<String>() private val isLocked: Boolean @@ -194,7 +188,7 @@ class ControlActionCoordinatorImpl @Inject constructor( } private fun vibrate(effect: VibrationEffect) { - bgExecutor.execute { vibrator.vibrate(effect) } + vibrator.vibrate(effect) } private fun showDetail(cvh: ControlViewHolder, pendingIntent: PendingIntent) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index bce878434aa1..1653e0adadb5 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -19,6 +19,7 @@ package com.android.systemui.dagger; import android.app.Activity; import com.android.systemui.ForegroundServicesDialog; +import com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity; import com.android.systemui.keyguard.WorkLockActivity; import com.android.systemui.people.PeopleSpaceActivity; import com.android.systemui.people.widget.LaunchConversationActivity; @@ -120,4 +121,11 @@ public abstract class DefaultActivityBinder { @IntoMap @ClassKey(TvUnblockSensorActivity.class) public abstract Activity bindTvUnblockSensorActivity(TvUnblockSensorActivity activity); + + /** Inject into HdmiCecSetMenuLanguageActivity. */ + @Binds + @IntoMap + @ClassKey(HdmiCecSetMenuLanguageActivity.class) + public abstract Activity bindHdmiCecSetMenuLanguageActivity( + HdmiCecSetMenuLanguageActivity activity); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index 96e2302f937c..ec2beb15959e 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -26,10 +26,15 @@ import com.android.systemui.accessibility.WindowMagnification; import com.android.systemui.biometrics.AuthController; import com.android.systemui.clipboardoverlay.ClipboardListener; import com.android.systemui.dreams.DreamOverlayRegistrant; +import com.android.systemui.dreams.SmartSpaceComplication; +import com.android.systemui.dreams.complication.DreamClockDateComplication; +import com.android.systemui.dreams.complication.DreamClockTimeComplication; +import com.android.systemui.dreams.complication.DreamWeatherComplication; import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.log.SessionTracker; +import com.android.systemui.media.dream.MediaDreamSentinel; import com.android.systemui.media.systemsounds.HomeSoundEffectController; import com.android.systemui.power.PowerUI; import com.android.systemui.privacy.television.TvOngoingPrivacyChip; @@ -218,4 +223,39 @@ public abstract class SystemUIBinder { @ClassKey(DreamOverlayRegistrant.class) public abstract CoreStartable bindDreamOverlayRegistrant( DreamOverlayRegistrant dreamOverlayRegistrant); + + /** Inject into SmartSpaceComplication.Registrant */ + @Binds + @IntoMap + @ClassKey(SmartSpaceComplication.Registrant.class) + public abstract CoreStartable bindSmartSpaceComplicationRegistrant( + SmartSpaceComplication.Registrant registrant); + + /** Inject into MediaDreamSentinel. */ + @Binds + @IntoMap + @ClassKey(MediaDreamSentinel.class) + public abstract CoreStartable bindMediaDreamSentinel( + MediaDreamSentinel sentinel); + + /** Inject into DreamClockTimeComplication.Registrant */ + @Binds + @IntoMap + @ClassKey(DreamClockTimeComplication.Registrant.class) + public abstract CoreStartable bindDreamClockTimeComplicationRegistrant( + DreamClockTimeComplication.Registrant registrant); + + /** Inject into DreamClockDateComplication.Registrant */ + @Binds + @IntoMap + @ClassKey(DreamClockDateComplication.Registrant.class) + public abstract CoreStartable bindDreamClockDateComplicationRegistrant( + DreamClockDateComplication.Registrant registrant); + + /** Inject into DreamWeatherComplication.Registrant */ + @Binds + @IntoMap + @ClassKey(DreamWeatherComplication.Registrant.class) + public abstract CoreStartable bindDreamWeatherComplicationRegistrant( + DreamWeatherComplication.Registrant registrant); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 3ee0cad32097..2160744c6803 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -88,16 +88,20 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ } }; + private DreamOverlayStateController mStateController; + @Inject public DreamOverlayService( Context context, @Main Executor executor, DreamOverlayComponent.Factory dreamOverlayComponentFactory, + DreamOverlayStateController stateController, KeyguardUpdateMonitor keyguardUpdateMonitor) { mContext = context; mExecutor = executor; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback); + mStateController = stateController; final DreamOverlayComponent component = dreamOverlayComponentFactory.create(mViewModelStore, mHost); @@ -118,6 +122,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ setCurrentState(Lifecycle.State.DESTROYED); final WindowManager windowManager = mContext.getSystemService(WindowManager.class); windowManager.removeView(mWindow.getDecorView()); + mStateController.setOverlayActive(false); super.onDestroy(); } @@ -127,6 +132,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mExecutor.execute(() -> { addOverlayWindowLocked(layoutParams); setCurrentState(Lifecycle.State.RESUMED); + mStateController.setOverlayActive(true); }); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index e83884819f70..ac7457d90e49 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -16,6 +16,8 @@ package com.android.systemui.dreams; +import android.util.Log; + import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; @@ -30,6 +32,8 @@ import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -41,6 +45,16 @@ import javax.inject.Inject; @SysUISingleton public class DreamOverlayStateController implements CallbackController<DreamOverlayStateController.Callback> { + private static final String TAG = "DreamOverlayStateCtlr"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0; + + private static final int OP_CLEAR_STATE = 1; + private static final int OP_SET_STATE = 2; + + private int mState; + /** * Callback for dream overlay events. */ @@ -50,11 +64,26 @@ public class DreamOverlayStateController implements */ default void onComplicationsChanged() { } + + /** + * Called when the dream overlay state changes. + */ + default void onStateChanged() { + } + + /** + * Called when the available complication types changes. + */ + default void onAvailableComplicationTypesChanged() { + } } private final Executor mExecutor; private final ArrayList<Callback> mCallbacks = new ArrayList<>(); + @Complication.ComplicationType + private int mAvailableComplicationTypes = Complication.COMPLICATION_TYPE_NONE; + private final Collection<Complication> mComplications = new HashSet(); @VisibleForTesting @@ -89,7 +118,33 @@ public class DreamOverlayStateController implements * Returns collection of present {@link Complication}. */ public Collection<Complication> getComplications() { - return Collections.unmodifiableCollection(mComplications); + return getComplications(true); + } + + /** + * Returns collection of present {@link Complication}. + */ + public Collection<Complication> getComplications(boolean filterByAvailability) { + return Collections.unmodifiableCollection(filterByAvailability + ? mComplications + .stream() + .filter(complication -> { + @Complication.ComplicationType + final int requiredTypes = complication.getRequiredTypeAvailability(); + + return requiredTypes == Complication.COMPLICATION_TYPE_NONE + || (requiredTypes & getAvailableComplicationTypes()) == requiredTypes; + }) + .collect(Collectors.toCollection(HashSet::new)) + : mComplications); + } + + private void notifyCallbacks(Consumer<Callback> callbackConsumer) { + mExecutor.execute(() -> { + for (Callback callback : mCallbacks) { + callbackConsumer.accept(callback); + } + }); } @Override @@ -117,4 +172,58 @@ public class DreamOverlayStateController implements mCallbacks.remove(callback); }); } + + /** + * Returns whether the overlay is active. + * @return {@code true} if overlay is active, {@code false} otherwise. + */ + public boolean isOverlayActive() { + return containsState(STATE_DREAM_OVERLAY_ACTIVE); + } + + private boolean containsState(int state) { + return (mState & state) != 0; + } + + private void modifyState(int op, int state) { + final int existingState = mState; + switch (op) { + case OP_CLEAR_STATE: + mState &= ~state; + break; + case OP_SET_STATE: + mState |= state; + break; + } + + if (existingState != mState) { + notifyCallbacks(callback -> callback.onStateChanged()); + } + } + + /** + * Sets whether the overlay is active. + * @param active {@code true} if overlay is active, {@code false} otherwise. + */ + public void setOverlayActive(boolean active) { + modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE); + } + + /** + * Returns the available complication types. + */ + @Complication.ComplicationType + public int getAvailableComplicationTypes() { + return mAvailableComplicationTypes; + } + + /** + * Sets the available complication types for the dream overlay. + */ + public void setAvailableComplicationTypes(@Complication.ComplicationType int types) { + mExecutor.execute(() -> { + mAvailableComplicationTypes = types; + mCallbacks.forEach(callback -> callback.onAvailableComplicationTypesChanged()); + }); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java new file mode 100644 index 000000000000..09221b4f810a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.android.systemui.CoreStartable; +import com.android.systemui.dreams.complication.Complication; +import com.android.systemui.dreams.complication.ComplicationLayoutParams; +import com.android.systemui.dreams.complication.ComplicationViewModel; +import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; + +import javax.inject.Inject; + +/** + * {@link SmartSpaceComplication} embodies the SmartSpace view found on the lockscreen as a + * {@link Complication} + */ +public class SmartSpaceComplication implements Complication { + /** + * {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with + * SystemUI. + */ + public static class Registrant extends CoreStartable { + private final LockscreenSmartspaceController mSmartSpaceController; + private final DreamOverlayStateController mDreamOverlayStateController; + private final SmartSpaceComplication mComplication; + + /** + * Default constructor for {@link SmartSpaceComplication}. + */ + @Inject + public Registrant(Context context, + DreamOverlayStateController dreamOverlayStateController, + SmartSpaceComplication smartSpaceComplication, + LockscreenSmartspaceController smartSpaceController) { + super(context); + mDreamOverlayStateController = dreamOverlayStateController; + mComplication = smartSpaceComplication; + mSmartSpaceController = smartSpaceController; + } + + @Override + public void start() { + if (mSmartSpaceController.isEnabled()) { + mDreamOverlayStateController.addComplication(mComplication); + } + } + } + + private static class SmartSpaceComplicationViewHolder implements ViewHolder { + private final LockscreenSmartspaceController mSmartSpaceController; + private final Context mContext; + + protected SmartSpaceComplicationViewHolder( + Context context, + LockscreenSmartspaceController smartSpaceController) { + mSmartSpaceController = smartSpaceController; + mContext = context; + } + + @Override + public View getView() { + final FrameLayout smartSpaceContainer = new FrameLayout(mContext); + smartSpaceContainer.addView( + mSmartSpaceController.buildAndConnectView(smartSpaceContainer), + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + + return smartSpaceContainer; + } + + @Override + public ComplicationLayoutParams getLayoutParams() { + return new ComplicationLayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, + ComplicationLayoutParams.POSITION_TOP | ComplicationLayoutParams.POSITION_START, + ComplicationLayoutParams.DIRECTION_DOWN, + 0, true); + } + } + + private final LockscreenSmartspaceController mSmartSpaceController; + private final Context mContext; + + @Inject + public SmartSpaceComplication(Context context, + LockscreenSmartspaceController smartSpaceController) { + mContext = context; + mSmartSpaceController = smartSpaceController; + } + + @Override + public ViewHolder createView(ComplicationViewModel model) { + return new SmartSpaceComplicationViewHolder(mContext, mSmartSpaceController); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java index 96cf50d58d10..fe458f4c5318 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java @@ -154,6 +154,27 @@ public interface Complication { int CATEGORY_SYSTEM = 1 << 1; /** + * The type of dream complications which can be provided by a {@link Complication}. + */ + @IntDef(prefix = {"COMPLICATION_TYPE_"}, flag = true, value = { + COMPLICATION_TYPE_NONE, + COMPLICATION_TYPE_TIME, + COMPLICATION_TYPE_DATE, + COMPLICATION_TYPE_WEATHER, + COMPLICATION_TYPE_AIR_QUALITY, + COMPLICATION_TYPE_CAST_INFO + }) + @Retention(RetentionPolicy.SOURCE) + @interface ComplicationType {} + + int COMPLICATION_TYPE_NONE = 0; + int COMPLICATION_TYPE_TIME = 1; + int COMPLICATION_TYPE_DATE = 1 << 1; + int COMPLICATION_TYPE_WEATHER = 1 << 2; + int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3; + int COMPLICATION_TYPE_CAST_INFO = 1 << 4; + + /** * The {@link Host} interface specifies a way a {@link Complication} to communicate with its * parent entity for information and actions. */ @@ -207,4 +228,15 @@ public interface Complication { * @return a {@link ViewHolder} for this {@link Complication} instance. */ ViewHolder createView(ComplicationViewModel model); + + /** + * Returns the types that must be present in order for this complication to participate on + * the dream overlay. By default, this method returns + * {@code Complication.COMPLICATION_TYPE_NONE} to indicate no types are required. + * @return + */ + @Complication.ComplicationType + default int getRequiredTypeAvailability() { + return Complication.COMPLICATION_TYPE_NONE; + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java index 76818fa0c42e..f6fe8d2e579c 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveData.java @@ -42,6 +42,10 @@ public class ComplicationCollectionLiveData extends LiveData<Collection<Complica setValue(mDreamOverlayStateController.getComplications()); } + @Override + public void onAvailableComplicationTypesChanged() { + setValue(mDreamOverlayStateController.getComplications()); + } }; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java index cb24ae609eeb..5223f379508f 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java @@ -25,10 +25,13 @@ import android.view.ViewGroup; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.Constraints; +import com.android.systemui.R; + import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Named; @@ -102,6 +105,8 @@ public class ComplicationLayoutEngine { final int direction = getLayoutParams().getDirection(); + final boolean snapsToGuide = getLayoutParams().snapsToGuide(); + // If no parent, view is the anchor. In this case, it is given the highest priority for // alignment. All alignment preferences are done in relation to the parent container. final boolean isRoot = head == mView; @@ -125,6 +130,11 @@ public class ComplicationLayoutEngine { } else { params.startToEnd = head.getId(); } + if (snapsToGuide + && (direction == ComplicationLayoutParams.DIRECTION_DOWN + || direction == ComplicationLayoutParams.DIRECTION_UP)) { + params.endToStart = R.id.complication_start_guide; + } break; case ComplicationLayoutParams.POSITION_TOP: if (isRoot || direction != ComplicationLayoutParams.DIRECTION_DOWN) { @@ -132,6 +142,11 @@ public class ComplicationLayoutEngine { } else { params.topToBottom = head.getId(); } + if (snapsToGuide + && (direction == ComplicationLayoutParams.DIRECTION_END + || direction == ComplicationLayoutParams.DIRECTION_START)) { + params.endToStart = R.id.complication_top_guide; + } break; case ComplicationLayoutParams.POSITION_BOTTOM: if (isRoot || direction != ComplicationLayoutParams.DIRECTION_UP) { @@ -139,6 +154,11 @@ public class ComplicationLayoutEngine { } else { params.bottomToTop = head.getId(); } + if (snapsToGuide + && (direction == ComplicationLayoutParams.DIRECTION_END + || direction == ComplicationLayoutParams.DIRECTION_START)) { + params.topToBottom = R.id.complication_bottom_guide; + } break; case ComplicationLayoutParams.POSITION_END: if (isRoot || direction != ComplicationLayoutParams.DIRECTION_START) { @@ -146,6 +166,11 @@ public class ComplicationLayoutEngine { } else { params.endToStart = head.getId(); } + if (snapsToGuide + && (direction == ComplicationLayoutParams.DIRECTION_UP + || direction == ComplicationLayoutParams.DIRECTION_DOWN)) { + params.startToEnd = R.id.complication_end_guide; + } break; } }); @@ -153,6 +178,16 @@ public class ComplicationLayoutEngine { mView.setLayoutParams(params); } + private void setGuide(ConstraintLayout.LayoutParams lp, int validDirections, + Consumer<ConstraintLayout.LayoutParams> consumer) { + final ComplicationLayoutParams layoutParams = getLayoutParams(); + if (!layoutParams.snapsToGuide()) { + return; + } + + consumer.accept(lp); + } + /** * Informs the {@link ViewEntry}'s parent entity to remove the {@link ViewEntry} from * being shown further. diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java index f9a69fadfedc..8e8cb72d6ad0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java @@ -40,13 +40,13 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { @interface Position {} /** Align view with the top of parent or bottom of preceding {@link Complication}. */ - static final int POSITION_TOP = 1 << 0; + public static final int POSITION_TOP = 1 << 0; /** Align view with the bottom of parent or top of preceding {@link Complication}. */ - static final int POSITION_BOTTOM = 1 << 1; + public static final int POSITION_BOTTOM = 1 << 1; /** Align view with the start of parent or end of preceding {@link Complication}. */ - static final int POSITION_START = 1 << 2; + public static final int POSITION_START = 1 << 2; /** Align view with the end of parent or start of preceding {@link Complication}. */ - static final int POSITION_END = 1 << 3; + public static final int POSITION_END = 1 << 3; private static final int FIRST_POSITION = POSITION_TOP; private static final int LAST_POSITION = POSITION_END; @@ -61,13 +61,13 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { @interface Direction {} /** Position view upward from position. */ - static final int DIRECTION_UP = 1 << 0; + public static final int DIRECTION_UP = 1 << 0; /** Position view downward from position. */ - static final int DIRECTION_DOWN = 1 << 1; + public static final int DIRECTION_DOWN = 1 << 1; /** Position view towards the start of the parent. */ - static final int DIRECTION_START = 1 << 2; + public static final int DIRECTION_START = 1 << 2; /** Position view towards the end of parent. */ - static final int DIRECTION_END = 1 << 3; + public static final int DIRECTION_END = 1 << 3; @Position private final int mPosition; @@ -77,6 +77,8 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { private final int mWeight; + private final boolean mSnapToGuide; + // Do not allow specifying opposite positions private static final int[] INVALID_POSITIONS = { POSITION_BOTTOM | POSITION_TOP, POSITION_END | POSITION_START }; @@ -104,6 +106,27 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { */ public ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight) { + this(width, height, position, direction, weight, false); + } + + /** + * Constructs a {@link ComplicationLayoutParams}. + * @param width The width {@link android.view.View.MeasureSpec} for the view. + * @param height The height {@link android.view.View.MeasureSpec} for the view. + * @param position The place within the parent container where the view should be positioned. + * @param direction The direction the view should be laid out from either the parent container + * or preceding view. + * @param weight The weight that should be considered for this view when compared to other + * views. This has an impact on the placement of the view but not the rendering of + * the view. + * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction + * will be automatically set to align with a predetermined guide for that + * side. For example, if the complication is aligned to the top end and + * direction is down, then the width of the complication will be set to span + * from the end of the parent to the guide. + */ + public ComplicationLayoutParams(int width, int height, @Position int position, + @Direction int direction, int weight, boolean snapToGuide) { super(width, height); if (!validatePosition(position)) { @@ -118,6 +141,8 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { mDirection = direction; mWeight = weight; + + mSnapToGuide = snapToGuide; } /** @@ -128,6 +153,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { mPosition = source.mPosition; mDirection = source.mDirection; mWeight = source.mWeight; + mSnapToGuide = source.mSnapToGuide; } private static boolean validateDirection(@Position int position, @Direction int direction) { @@ -180,7 +206,19 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { return mPosition; } + /** + * Returns the set weight for the complication. The weight determines ordering a complication + * given the same position/direction. + */ public int getWeight() { return mWeight; } + + /** + * Returns whether the complication's dimension perpendicular to direction should be + * automatically set. + */ + public boolean snapsToGuide() { + return mSnapToGuide; + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java new file mode 100644 index 000000000000..3a2a6ef60f03 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication; + +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER; + +import com.android.settingslib.dream.DreamBackend; + +/** + * A collection of utility methods for working with {@link Complication}. + */ +public class ComplicationUtils { + /** + * Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to + * {@link ComplicationType}. + */ + @Complication.ComplicationType + public static int convertComplicationType(@DreamBackend.ComplicationType int type) { + switch (type) { + case DreamBackend.COMPLICATION_TYPE_TIME: + return COMPLICATION_TYPE_TIME; + case DreamBackend.COMPLICATION_TYPE_DATE: + return COMPLICATION_TYPE_DATE; + case DreamBackend.COMPLICATION_TYPE_WEATHER: + return COMPLICATION_TYPE_WEATHER; + case DreamBackend.COMPLICATION_TYPE_AIR_QUALITY: + return COMPLICATION_TYPE_AIR_QUALITY; + case DreamBackend.COMPLICATION_TYPE_CAST_INFO: + return COMPLICATION_TYPE_CAST_INFO; + default: + return COMPLICATION_TYPE_NONE; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java new file mode 100644 index 000000000000..59c52b9e402b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockDateComplication.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication; + +import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS; +import static com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent.DreamClockDateComplicationModule.DREAM_CLOCK_DATE_COMPLICATION_VIEW; + +import android.content.Context; +import android.view.View; + +import com.android.systemui.CoreStartable; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.complication.dagger.DreamClockDateComplicationComponent; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Clock Date Complication that produce Clock Date view holder. + */ +public class DreamClockDateComplication implements Complication { + DreamClockDateComplicationComponent.Factory mComponentFactory; + + /** + * Default constructor for {@link DreamClockDateComplication}. + */ + @Inject + public DreamClockDateComplication( + DreamClockDateComplicationComponent.Factory componentFactory) { + mComponentFactory = componentFactory; + } + + /** + * Create {@link DreamClockDateViewHolder}. + */ + @Override + public ViewHolder createView(ComplicationViewModel model) { + return mComponentFactory.create().getViewHolder(); + } + + /** + * {@link CoreStartable} responsbile for registering {@link DreamClockDateComplication} with + * SystemUI. + */ + public static class Registrant extends CoreStartable { + private final DreamOverlayStateController mDreamOverlayStateController; + private final DreamClockDateComplication mComplication; + + /** + * Default constructor to register {@link DreamClockDateComplication}. + */ + @Inject + public Registrant(Context context, + DreamOverlayStateController dreamOverlayStateController, + DreamClockDateComplication dreamClockDateComplication) { + super(context); + mDreamOverlayStateController = dreamOverlayStateController; + mComplication = dreamClockDateComplication; + } + + @Override + public void start() { + mDreamOverlayStateController.addComplication(mComplication); + } + } + + /** + * ViewHolder to contain value/logic associated with a Clock Date Complication View. + */ + public static class DreamClockDateViewHolder implements ViewHolder { + private final View mView; + private final ComplicationLayoutParams mLayoutParams; + + @Inject + DreamClockDateViewHolder(@Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW) View view, + @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS) + ComplicationLayoutParams layoutParams) { + mView = view; + mLayoutParams = layoutParams; + } + + @Override + public View getView() { + return mView; + } + + @Override + public ComplicationLayoutParams getLayoutParams() { + return mLayoutParams; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java new file mode 100644 index 000000000000..b0c3a424cc92 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication; + +import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS; +import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW; + +import android.content.Context; +import android.view.View; + +import com.android.systemui.CoreStartable; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationComponent; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Clock Time Complication that produce Clock Time view holder. + */ +public class DreamClockTimeComplication implements Complication { + DreamClockTimeComplicationComponent.Factory mComponentFactory; + + /** + * Default constructor for {@link DreamClockTimeComplication}. + */ + @Inject + public DreamClockTimeComplication( + DreamClockTimeComplicationComponent.Factory componentFactory) { + mComponentFactory = componentFactory; + } + + /** + * Create {@link DreamClockTimeViewHolder}. + */ + @Override + public ViewHolder createView(ComplicationViewModel model) { + return mComponentFactory.create().getViewHolder(); + } + + /** + * {@link CoreStartable} responsbile for registering {@link DreamClockTimeComplication} with + * SystemUI. + */ + public static class Registrant extends CoreStartable { + private final DreamOverlayStateController mDreamOverlayStateController; + private final DreamClockTimeComplication mComplication; + + /** + * Default constructor to register {@link DreamClockTimeComplication}. + */ + @Inject + public Registrant(Context context, + DreamOverlayStateController dreamOverlayStateController, + DreamClockTimeComplication dreamClockTimeComplication) { + super(context); + mDreamOverlayStateController = dreamOverlayStateController; + mComplication = dreamClockTimeComplication; + } + + @Override + public void start() { + mDreamOverlayStateController.addComplication(mComplication); + } + } + + /** + * ViewHolder to contain value/logic associated with a Clock Time Complication View. + */ + public static class DreamClockTimeViewHolder implements ViewHolder { + private final View mView; + private final ComplicationLayoutParams mLayoutParams; + + @Inject + DreamClockTimeViewHolder(@Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) View view, + @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS) + ComplicationLayoutParams layoutParams) { + mView = view; + mLayoutParams = layoutParams; + } + + @Override + public View getView() { + return mView; + } + + @Override + public ComplicationLayoutParams getLayoutParams() { + return mLayoutParams; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java new file mode 100644 index 000000000000..cbdbef3ae57e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication; + +import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS; +import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_VIEW; + +import android.app.smartspace.SmartspaceAction; +import android.app.smartspace.SmartspaceTarget; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.text.TextUtils; +import android.widget.TextView; + +import com.android.systemui.CoreStartable; +import com.android.systemui.R; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent; +import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener; +import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; +import com.android.systemui.util.ViewController; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Weather Complication that produce Weather view holder. + */ +public class DreamWeatherComplication implements Complication { + DreamWeatherComplicationComponent.Factory mComponentFactory; + + /** + * Default constructor for {@link DreamWeatherComplication}. + */ + @Inject + public DreamWeatherComplication( + DreamWeatherComplicationComponent.Factory componentFactory) { + mComponentFactory = componentFactory; + } + + /** + * Create {@link DreamWeatherViewHolder}. + */ + @Override + public ViewHolder createView(ComplicationViewModel model) { + return mComponentFactory.create().getViewHolder(); + } + + /** + * {@link CoreStartable} for registering {@link DreamWeatherComplication} with SystemUI. + */ + public static class Registrant extends CoreStartable { + private final LockscreenSmartspaceController mSmartSpaceController; + private final DreamOverlayStateController mDreamOverlayStateController; + private final DreamWeatherComplication mComplication; + + /** + * Default constructor to register {@link DreamWeatherComplication}. + */ + @Inject + public Registrant(Context context, + LockscreenSmartspaceController smartspaceController, + DreamOverlayStateController dreamOverlayStateController, + DreamWeatherComplication dreamWeatherComplication) { + super(context); + mSmartSpaceController = smartspaceController; + mDreamOverlayStateController = dreamOverlayStateController; + mComplication = dreamWeatherComplication; + } + + @Override + public void start() { + if (mSmartSpaceController.isEnabled()) { + mDreamOverlayStateController.addComplication(mComplication); + } + } + } + + /** + * ViewHolder to contain value/logic associated with a Weather Complication View. + */ + public static class DreamWeatherViewHolder implements ViewHolder { + private final TextView mView; + private final ComplicationLayoutParams mLayoutParams; + private final DreamWeatherViewController mViewController; + + @Inject + DreamWeatherViewHolder( + @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view, + DreamWeatherViewController controller, + @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS) + ComplicationLayoutParams layoutParams) { + mView = view; + mLayoutParams = layoutParams; + mViewController = controller; + mViewController.init(); + } + + @Override + public TextView getView() { + return mView; + } + + @Override + public ComplicationLayoutParams getLayoutParams() { + return mLayoutParams; + } + } + + /** + * ViewController to contain value/logic associated with a Weather Complication View. + */ + static class DreamWeatherViewController extends ViewController<TextView> { + private final LockscreenSmartspaceController mSmartSpaceController; + private SmartspaceTargetListener mSmartspaceTargetListener; + + @Inject + DreamWeatherViewController( + @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view, + LockscreenSmartspaceController smartspaceController + ) { + super(view); + mSmartSpaceController = smartspaceController; + } + + @Override + protected void onViewAttached() { + mSmartspaceTargetListener = targets -> targets.forEach( + t -> { + if (t instanceof SmartspaceTarget + && ((SmartspaceTarget) t).getFeatureType() + == SmartspaceTarget.FEATURE_WEATHER) { + final SmartspaceTarget target = (SmartspaceTarget) t; + final SmartspaceAction headerAction = target.getHeaderAction(); + if (headerAction == null || TextUtils.isEmpty( + headerAction.getTitle())) { + return; + } + + String temperature = headerAction.getTitle().toString(); + mView.setText(temperature); + final Icon icon = headerAction.getIcon(); + if (icon != null) { + final int iconSize = + getResources().getDimensionPixelSize( + R.dimen.smart_action_button_icon_size); + final Drawable iconDrawable = icon.loadDrawable(getContext()); + iconDrawable.setBounds(0, 0, iconSize, iconSize); + mView.setCompoundDrawables(iconDrawable, null, null, null); + mView.setCompoundDrawablePadding( + getResources().getDimensionPixelSize( + R.dimen.smart_action_button_icon_padding)); + + } + } + }); + mSmartSpaceController.addListener(mSmartspaceTargetListener); + } + + @Override + protected void onViewDetached() { + mSmartSpaceController.removeListener(mSmartspaceTargetListener); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java new file mode 100644 index 000000000000..eaffb1ca5b3e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationComponent.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication.dagger; + + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.util.Preconditions; +import com.android.systemui.R; +import com.android.systemui.dreams.complication.ComplicationLayoutParams; +import com.android.systemui.dreams.complication.DreamClockDateComplication.DreamClockDateViewHolder; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Named; +import javax.inject.Scope; + +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * {@link DreamClockDateComplicationComponent} is responsible for generating dependencies + * surrounding the + * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout + * details. + */ +@Subcomponent(modules = { + DreamClockDateComplicationComponent.DreamClockDateComplicationModule.class, +}) +@DreamClockDateComplicationComponent.DreamClockDateComplicationScope +public interface DreamClockDateComplicationComponent { + /** + * Creates {@link DreamClockDateViewHolder}. + */ + DreamClockDateViewHolder getViewHolder(); + + @Documented + @Retention(RUNTIME) + @Scope + @interface DreamClockDateComplicationScope { + } + + /** + * Generates {@link DreamClockDateComplicationComponent}. + */ + @Subcomponent.Factory + interface Factory { + DreamClockDateComplicationComponent create(); + } + + /** + * Scoped values for {@link DreamClockDateComplicationComponent}. + */ + @Module + interface DreamClockDateComplicationModule { + String DREAM_CLOCK_DATE_COMPLICATION_VIEW = "clock_date_complication_view"; + String DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS = + "clock_date_complication_layout_params"; + // Order weight of insert into parent container + int INSERT_ORDER_WEIGHT = 2; + + /** + * Provides the complication view. + */ + @Provides + @DreamClockDateComplicationScope + @Named(DREAM_CLOCK_DATE_COMPLICATION_VIEW) + static View provideComplicationView(LayoutInflater layoutInflater) { + return Preconditions.checkNotNull( + layoutInflater.inflate(R.layout.dream_overlay_complication_clock_date, + null, false), + "R.layout.dream_overlay_complication_clock_date did not properly inflated"); + } + + /** + * Provides the layout parameters for the complication view. + */ + @Provides + @DreamClockDateComplicationScope + @Named(DREAM_CLOCK_DATE_COMPLICATION_LAYOUT_PARAMS) + static ComplicationLayoutParams provideLayoutParams() { + return new ComplicationLayoutParams(0, + ViewGroup.LayoutParams.WRAP_CONTENT, + ComplicationLayoutParams.POSITION_BOTTOM + | ComplicationLayoutParams.POSITION_START, + ComplicationLayoutParams.DIRECTION_END, + INSERT_ORDER_WEIGHT, + true); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java new file mode 100644 index 000000000000..053c5b345760 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationComponent.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication.dagger; + + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.android.internal.util.Preconditions; +import com.android.systemui.R; +import com.android.systemui.dreams.complication.ComplicationLayoutParams; +import com.android.systemui.dreams.complication.DreamClockTimeComplication.DreamClockTimeViewHolder; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Named; +import javax.inject.Scope; + +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * {@link DreamClockTimeComplicationComponent} is responsible for generating dependencies + * surrounding the + * Clock Time {@link com.android.systemui.dreams.complication.Complication}, such as the layout + * details. + */ +@Subcomponent(modules = { + DreamClockTimeComplicationComponent.DreamClockTimeComplicationModule.class, +}) +@DreamClockTimeComplicationComponent.DreamClockTimeComplicationScope +public interface DreamClockTimeComplicationComponent { + /** + * Creates {@link DreamClockTimeViewHolder}. + */ + DreamClockTimeViewHolder getViewHolder(); + + @Documented + @Retention(RUNTIME) + @Scope + @interface DreamClockTimeComplicationScope { + } + + /** + * Generates {@link DreamClockTimeComplicationComponent}. + */ + @Subcomponent.Factory + interface Factory { + DreamClockTimeComplicationComponent create(); + } + + /** + * Scoped values for {@link DreamClockTimeComplicationComponent}. + */ + @Module + interface DreamClockTimeComplicationModule { + String DREAM_CLOCK_TIME_COMPLICATION_VIEW = "clock_time_complication_view"; + String DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS = + "clock_time_complication_layout_params"; + // Order weight of insert into parent container + int INSERT_ORDER_WEIGHT = 0; + + /** + * Provides the complication view. + */ + @Provides + @DreamClockTimeComplicationScope + @Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW) + static View provideComplicationView(LayoutInflater layoutInflater) { + return Preconditions.checkNotNull( + layoutInflater.inflate(R.layout.dream_overlay_complication_clock_time, + null, false), + "R.layout.dream_overlay_complication_clock_time did not properly inflated"); + } + + /** + * Provides the layout parameters for the complication view. + */ + @Provides + @DreamClockTimeComplicationScope + @Named(DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS) + static ComplicationLayoutParams provideLayoutParams() { + return new ComplicationLayoutParams(0, + ViewGroup.LayoutParams.WRAP_CONTENT, + ComplicationLayoutParams.POSITION_BOTTOM + | ComplicationLayoutParams.POSITION_START, + ComplicationLayoutParams.DIRECTION_UP, + INSERT_ORDER_WEIGHT, + true); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java new file mode 100644 index 000000000000..a282594ff282 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication.dagger; + + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.internal.util.Preconditions; +import com.android.systemui.R; +import com.android.systemui.dreams.complication.ComplicationLayoutParams; +import com.android.systemui.dreams.complication.DreamWeatherComplication.DreamWeatherViewHolder; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Named; +import javax.inject.Scope; + +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * {@link DreamWeatherComplicationComponent} is responsible for generating dependencies surrounding + * the + * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout + * details. + */ +@Subcomponent(modules = { + DreamWeatherComplicationComponent.DreamWeatherComplicationModule.class, +}) +@DreamWeatherComplicationComponent.DreamWeatherComplicationScope +public interface DreamWeatherComplicationComponent { + /** + * Creates {@link DreamWeatherViewHolder}. + */ + DreamWeatherViewHolder getViewHolder(); + + @Documented + @Retention(RUNTIME) + @Scope + @interface DreamWeatherComplicationScope { + } + + /** + * Generates {@link DreamWeatherComplicationComponent}. + */ + @Subcomponent.Factory + interface Factory { + DreamWeatherComplicationComponent create(); + } + + /** + * Scoped values for {@link DreamWeatherComplicationComponent}. + */ + @Module + interface DreamWeatherComplicationModule { + String DREAM_WEATHER_COMPLICATION_VIEW = "weather_complication_view"; + String DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS = + "weather_complication_layout_params"; + // Order weight of insert into parent container + int INSERT_ORDER_WEIGHT = 1; + + /** + * Provides the complication view. + */ + @Provides + @DreamWeatherComplicationScope + @Named(DREAM_WEATHER_COMPLICATION_VIEW) + static TextView provideComplicationView(LayoutInflater layoutInflater) { + return Preconditions.checkNotNull((TextView) + layoutInflater.inflate(R.layout.dream_overlay_complication_weather, + null, false), + "R.layout.dream_overlay_complication_weather did not properly inflated"); + } + + /** + * Provides the layout parameters for the complication view. + */ + @Provides + @DreamWeatherComplicationScope + @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS) + static ComplicationLayoutParams provideLayoutParams() { + return new ComplicationLayoutParams(0, + ViewGroup.LayoutParams.WRAP_CONTENT, + ComplicationLayoutParams.POSITION_BOTTOM + | ComplicationLayoutParams.POSITION_START, + ComplicationLayoutParams.DIRECTION_END, + INSERT_ORDER_WEIGHT, + true); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java new file mode 100644 index 000000000000..8e4fb3783f4a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication.dagger; + +import com.android.systemui.dagger.SystemUIBinder; + +import dagger.Module; + +/** + * Module for all components with corresponding dream layer complications registered in + * {@link SystemUIBinder}. + */ +@Module(subcomponents = { + DreamClockTimeComplicationComponent.class, + DreamClockDateComplicationComponent.class, + DreamWeatherComplicationComponent.class, +}) +public interface RegisteredComplicationsModule { +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index 3d2f924563f3..d8af9e5f1f4a 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -16,6 +16,7 @@ package com.android.systemui.dreams.dagger; +import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule; import com.android.systemui.dreams.touch.dagger.DreamTouchModule; import dagger.Module; @@ -25,6 +26,7 @@ import dagger.Module; */ @Module(includes = { DreamTouchModule.class, + RegisteredComplicationsModule.class, }, subcomponents = { DreamOverlayComponent.class, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index 503817a23f7f..4eb5cb97607a 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -34,7 +34,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.DreamOverlayContainerView; import com.android.systemui.dreams.DreamOverlayStatusBarView; -import com.android.systemui.dreams.touch.DreamTouchHandler; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; @@ -44,7 +43,6 @@ import javax.inject.Named; import dagger.Lazy; import dagger.Module; import dagger.Provides; -import dagger.multibindings.IntoSet; /** Dagger module for {@link DreamOverlayComponent}. */ @Module @@ -149,12 +147,4 @@ public abstract class DreamOverlayModule { static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) { return lifecycleOwner.getLifecycle(); } - - // TODO: This stub should be removed once there is a {@link DreamTouchHandler} - // implementation present. - @Provides - @IntoSet - static DreamTouchHandler provideDreamTouchHandler() { - return session -> { }; - } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java new file mode 100644 index 000000000000..d16c8c8c59d6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.touch; + +import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING; +import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING; +import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION; + +import android.animation.ValueAnimator; +import android.util.Log; +import android.view.GestureDetector; +import android.view.InputEvent; +import android.view.MotionEvent; +import android.view.VelocityTracker; + +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.KeyguardBouncer; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.wm.shell.animation.FlingAnimationUtils; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Monitor for tracking touches on the DreamOverlay to bring up the bouncer. + */ +public class BouncerSwipeTouchHandler implements DreamTouchHandler { + /** + * An interface for creating ValueAnimators. + */ + public interface ValueAnimatorCreator { + /** + * Creates {@link ValueAnimator}. + */ + ValueAnimator create(float start, float finish); + } + + /** + * An interface for obtaining VelocityTrackers. + */ + public interface VelocityTrackerFactory { + /** + * Obtains {@link VelocityTracker}. + */ + VelocityTracker obtain(); + } + + public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f; + + private static final String TAG = "BouncerSwipeTouchHandler"; + private final NotificationShadeWindowController mNotificationShadeWindowController; + private final float mBouncerZoneScreenPercentage; + + private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private float mCurrentExpansion; + private final StatusBar mStatusBar; + + private VelocityTracker mVelocityTracker; + + private final FlingAnimationUtils mFlingAnimationUtils; + private final FlingAnimationUtils mFlingAnimationUtilsClosing; + + private Boolean mCapture; + + private TouchSession mTouchSession; + + private ValueAnimatorCreator mValueAnimatorCreator; + + private VelocityTrackerFactory mVelocityTrackerFactory; + + private final GestureDetector.OnGestureListener mOnGestureListener = + new GestureDetector.SimpleOnGestureListener() { + boolean mTrack; + boolean mBouncerPresent; + + @Override + public boolean onDown(MotionEvent e) { + // We only consider gestures that originate from the lower portion of the + // screen. + final float displayHeight = mStatusBar.getDisplayHeight(); + + mBouncerPresent = mStatusBar.isBouncerShowing(); + + // The target zone is either at the top or bottom of the screen, dependent on + // whether the bouncer is present. + final float zonePercentage = + Math.abs(e.getY() - (mBouncerPresent ? 0 : displayHeight)) + / displayHeight; + + mTrack = zonePercentage < mBouncerZoneScreenPercentage; + + // Never capture onDown. While this might lead to some false positive touches + // being sent to other windows/layers, this is necessary to make sure the + // proper touch event sequence is received by others in the event we do not + // consume the sequence here. + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + // Do not handle scroll gestures if not tracking touch events. + if (!mTrack) { + return false; + } + + if (mCapture == null) { + // If the user scrolling favors a vertical direction, begin capturing + // scrolls. + mCapture = Math.abs(distanceY) > Math.abs(distanceX); + + if (mCapture) { + // Since the user is dragging the bouncer up, set scrimmed to false. + mStatusBarKeyguardViewManager.showBouncer(false); + } + } + + if (!mCapture) { + return false; + } + + // For consistency, we adopt the expansion definition found in the + // PanelViewController. In this case, expansion refers to the view above the + // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer + // is fully hidden at full expansion (1) and fully visible when fully collapsed + // (0). + final float screenTravelPercentage = + Math.abs((e1.getY() - e2.getY()) / mStatusBar.getDisplayHeight()); + setPanelExpansion( + mBouncerPresent ? screenTravelPercentage : 1 - screenTravelPercentage); + + return true; + } + }; + + private void setPanelExpansion(float expansion) { + mCurrentExpansion = expansion; + mStatusBarKeyguardViewManager.onPanelExpansionChanged(mCurrentExpansion, false, true); + } + + @Inject + public BouncerSwipeTouchHandler( + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + StatusBar statusBar, + NotificationShadeWindowController notificationShadeWindowController, + ValueAnimatorCreator valueAnimatorCreator, + VelocityTrackerFactory velocityTrackerFactory, + @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING) + FlingAnimationUtils flingAnimationUtils, + @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING) + FlingAnimationUtils flingAnimationUtilsClosing, + @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage) { + mStatusBar = statusBar; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mNotificationShadeWindowController = notificationShadeWindowController; + mBouncerZoneScreenPercentage = swipeRegionPercentage; + mFlingAnimationUtils = flingAnimationUtils; + mFlingAnimationUtilsClosing = flingAnimationUtilsClosing; + mValueAnimatorCreator = valueAnimatorCreator; + mVelocityTrackerFactory = velocityTrackerFactory; + } + + @Override + public void onSessionStart(TouchSession session) { + mVelocityTracker = mVelocityTrackerFactory.obtain(); + mTouchSession = session; + mVelocityTracker.clear(); + mNotificationShadeWindowController.setForcePluginOpen(true, this); + session.registerGestureListener(mOnGestureListener); + session.registerInputListener(ev -> onMotionEvent(ev)); + + } + + @Override + public void onSessionEnd(TouchSession session) { + mVelocityTracker.recycle(); + mCapture = null; + mNotificationShadeWindowController.setForcePluginOpen(false, this); + } + + private void onMotionEvent(InputEvent event) { + if (!(event instanceof MotionEvent)) { + Log.e(TAG, "non MotionEvent received:" + event); + return; + } + + final MotionEvent motionEvent = (MotionEvent) event; + + switch(motionEvent.getAction()) { + case MotionEvent.ACTION_UP: + // If we are not capturing any input, there is no need to consider animating to + // finish transition. + if (mCapture == null || !mCapture) { + break; + } + + // We must capture the resulting velocities as resetMonitor() will clear these + // values. + mVelocityTracker.computeCurrentVelocity(1000); + final float verticalVelocity = mVelocityTracker.getYVelocity(); + final float horizontalVelocity = mVelocityTracker.getXVelocity(); + + final float velocityVector = + (float) Math.hypot(horizontalVelocity, verticalVelocity); + + + final float expansion = flingRevealsOverlay(verticalVelocity, velocityVector) + ? KeyguardBouncer.EXPANSION_HIDDEN : KeyguardBouncer.EXPANSION_VISIBLE; + flingToExpansion(verticalVelocity, expansion); + + if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) { + mStatusBarKeyguardViewManager.reset(false); + } + mTouchSession.pop(); + break; + default: + mVelocityTracker.addMovement(motionEvent); + break; + } + } + + private ValueAnimator createExpansionAnimator(float targetExpansion) { + final ValueAnimator animator = + mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion); + animator.addUpdateListener( + animation -> { + setPanelExpansion((float) animation.getAnimatedValue()); + }); + return animator; + } + + protected boolean flingRevealsOverlay(float velocity, float velocityVector) { + // Fully expand if the user has expanded the bouncer less than halfway or final velocity was + // positive, indicating an downward direction. + if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { + return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD; + } else { + return velocity > 0; + } + } + + protected void flingToExpansion(float velocity, float expansion) { + final float viewHeight = mStatusBar.getDisplayHeight(); + final float currentHeight = viewHeight * mCurrentExpansion; + final float targetHeight = viewHeight * expansion; + + final ValueAnimator animator = createExpansionAnimator(expansion); + if (expansion == KeyguardBouncer.EXPANSION_HIDDEN) { + // The animation utils deal in pixel units, rather than expansion height. + mFlingAnimationUtils.apply(animator, currentHeight, targetHeight, velocity, viewHeight); + } else { + mFlingAnimationUtilsClosing.apply( + animator, mCurrentExpansion, currentHeight, targetHeight, viewHeight); + } + + animator.start(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java new file mode 100644 index 000000000000..b9436f96c74f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.touch.dagger; + +import android.animation.ValueAnimator; +import android.content.res.Resources; +import android.util.TypedValue; +import android.view.VelocityTracker; + +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler; +import com.android.systemui.dreams.touch.DreamTouchHandler; +import com.android.systemui.statusbar.phone.PanelViewController; +import com.android.wm.shell.animation.FlingAnimationUtils; + +import javax.inject.Named; +import javax.inject.Provider; + +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoSet; + +/** + * This module captures the components associated with {@link BouncerSwipeTouchHandler}. + */ +@Module +public class BouncerSwipeModule { + /** + * The region, defined as the percentage of the screen, from which a touch gesture to start + * swiping up to the bouncer can occur. + */ + public static final String SWIPE_TO_BOUNCER_START_REGION = "swipe_to_bouncer_start_region"; + + /** + * The {@link android.view.animation.AnimationUtils} for animating the bouncer closing. + */ + public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING = + "swipe_to_bouncer_fling_animation_utils_closing"; + + /** + * The {@link android.view.animation.AnimationUtils} for animating the bouncer opening. + */ + public static final String SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING = + "swipe_to_bouncer_fling_animation_utils_opening"; + + /** + * Provides {@link BouncerSwipeTouchHandler} for inclusion in touch handling over the dream. + */ + @Provides + @IntoSet + public static DreamTouchHandler providesBouncerSwipeTouchHandler( + BouncerSwipeTouchHandler touchHandler) { + return touchHandler; + } + + /** + * Provides {@link android.view.animation.AnimationUtils} for closing. + */ + @Provides + @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING) + public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsClosing( + Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) { + return flingAnimationUtilsBuilderProvider.get() + .reset() + .setMaxLengthSeconds(PanelViewController.FLING_CLOSING_MAX_LENGTH_SECONDS) + .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR) + .build(); + } + + /** + * Provides {@link android.view.animation.AnimationUtils} for opening. + */ + @Provides + @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING) + public static FlingAnimationUtils providesSwipeToBouncerFlingAnimationUtilsOpening( + Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilderProvider) { + return flingAnimationUtilsBuilderProvider.get() + .reset() + .setMaxLengthSeconds(PanelViewController.FLING_MAX_LENGTH_SECONDS) + .setSpeedUpFactor(PanelViewController.FLING_SPEED_UP_FACTOR) + .build(); + } + + /** + * Provides the region to start swipe gestures from. + */ + @Provides + @Named(SWIPE_TO_BOUNCER_START_REGION) + public static float providesSwipeToBouncerStartRegion(@Main Resources resources) { + TypedValue typedValue = new TypedValue(); + resources.getValue(R.dimen.dream_overlay_bouncer_start_region_screen_percentage, + typedValue, true); + return typedValue.getFloat(); + } + + /** + * Provides the default {@link BouncerSwipeTouchHandler.ValueAnimatorCreator}, which is simply + * a wrapper around {@link ValueAnimator}. + */ + @Provides + public static BouncerSwipeTouchHandler.ValueAnimatorCreator providesValueAnimatorCreator() { + return (start, finish) -> ValueAnimator.ofFloat(start, finish); + } + + /** + * Provides the default {@link BouncerSwipeTouchHandler.VelocityTrackerFactory}. which is a + * passthrough to {@link android.view.VelocityTracker}. + */ + @Provides + public static BouncerSwipeTouchHandler.VelocityTrackerFactory providesVelocityTrackerFactory() { + return () -> VelocityTracker.obtain(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java index 7b77b593b330..dad0004613f6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java @@ -21,8 +21,10 @@ import dagger.Module; /** * {@link DreamTouchModule} encapsulates dream touch-related components. */ -@Module(subcomponents = { - InputSessionComponent.class, +@Module(includes = { + BouncerSwipeModule.class, + }, subcomponents = { + InputSessionComponent.class, }) public interface DreamTouchModule { String INPUT_SESSION_NAME = "INPUT_SESSION_NAME"; diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 97edf3bac4a8..c894b7023d75 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -112,6 +112,9 @@ public class Flags { public static final BooleanFlag COMBINED_STATUS_BAR_SIGNAL_ICONS = new BooleanFlag(601, false); + public static final BooleanFlag STATUS_BAR_USER_SWITCHER = + new BooleanFlag(602, false); + /***************************************/ // 700 - dialer/calls public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP = diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 2ebcd8531128..f0371fc1f0cd 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -62,7 +62,6 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; -import android.os.Vibrator; import android.provider.Settings; import android.service.dreams.IDreamManager; import android.sysprop.TelephonyProperties; @@ -119,6 +118,7 @@ import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.scrim.ScrimDrawable; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.phone.SystemUIDialogManager; @@ -327,7 +327,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene TelephonyListenerManager telephonyListenerManager, GlobalSettings globalSettings, SecureSettings secureSettings, - @Nullable Vibrator vibrator, + @NonNull VibratorHelper vibrator, @Main Resources resources, ConfigurationController configurationController, KeyguardStateController keyguardStateController, @@ -397,7 +397,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mGlobalSettings.registerContentObserver( Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, mAirplaneModeObserver); - mHasVibrator = vibrator != null && vibrator.hasVibrator(); + mHasVibrator = vibrator.hasVibrator(); mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean( R.bool.config_useFixedVolume); diff --git a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java new file mode 100644 index 000000000000..b304c3ca737a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageActivity.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.hdmi; + +import android.hardware.hdmi.HdmiControlManager; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.tv.TvBottomSheetActivity; + +import javax.inject.Inject; + +/** + * Confirmation dialog shown when Set Menu Language CEC message was received. + */ +public class HdmiCecSetMenuLanguageActivity extends TvBottomSheetActivity + implements View.OnClickListener { + private static final String TAG = HdmiCecSetMenuLanguageActivity.class.getSimpleName(); + + private final HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper; + + @Inject + public HdmiCecSetMenuLanguageActivity( + HdmiCecSetMenuLanguageHelper hdmiCecSetMenuLanguageHelper) { + mHdmiCecSetMenuLanguageHelper = hdmiCecSetMenuLanguageHelper; + } + + @Override + public final void onCreate(Bundle b) { + super.onCreate(b); + getWindow().addPrivateFlags( + WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + String languageTag = getIntent().getStringExtra(HdmiControlManager.EXTRA_LOCALE); + mHdmiCecSetMenuLanguageHelper.setLocale(languageTag); + if (mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()) { + finish(); + } + } + + @Override + public void onResume() { + super.onResume(); + CharSequence title = getString(R.string.hdmi_cec_set_menu_language_title, + mHdmiCecSetMenuLanguageHelper.getLocale().getDisplayLanguage()); + CharSequence text = getString(R.string.hdmi_cec_set_menu_language_description); + initUI(title, text); + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.bottom_sheet_positive_button) { + mHdmiCecSetMenuLanguageHelper.acceptLocale(); + } else { + mHdmiCecSetMenuLanguageHelper.declineLocale(); + } + finish(); + } + + void initUI(CharSequence title, CharSequence text) { + TextView titleTextView = findViewById(R.id.bottom_sheet_title); + TextView contentTextView = findViewById(R.id.bottom_sheet_body); + ImageView icon = findViewById(R.id.bottom_sheet_icon); + ImageView secondIcon = findViewById(R.id.bottom_sheet_second_icon); + Button okButton = findViewById(R.id.bottom_sheet_positive_button); + Button cancelButton = findViewById(R.id.bottom_sheet_negative_button); + + titleTextView.setText(title); + contentTextView.setText(text); + icon.setImageResource(com.android.internal.R.drawable.ic_settings_language); + secondIcon.setVisibility(View.GONE); + + okButton.setText(R.string.hdmi_cec_set_menu_language_accept); + okButton.setOnClickListener(this); + + cancelButton.setText(R.string.hdmi_cec_set_menu_language_decline); + cancelButton.setOnClickListener(this); + cancelButton.requestFocus(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java new file mode 100644 index 000000000000..1f58112c5dc6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelper.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.hdmi; + +import android.provider.Settings; + +import com.android.internal.app.LocalePicker; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.util.settings.SecureSettings; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Locale; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * Helper class to separate model and view for system language change initiated by HDMI CEC. + */ +@SysUISingleton +public class HdmiCecSetMenuLanguageHelper { + private static final String TAG = HdmiCecSetMenuLanguageHelper.class.getSimpleName(); + private static final String SEPARATOR = ","; + + private final Executor mBackgroundExecutor; + private final SecureSettings mSecureSettings; + + private Locale mLocale; + private HashSet<String> mDenylist; + + @Inject + public HdmiCecSetMenuLanguageHelper(@Background Executor executor, + SecureSettings secureSettings) { + mBackgroundExecutor = executor; + mSecureSettings = secureSettings; + String denylist = mSecureSettings.getString( + Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST); + mDenylist = new HashSet<>(denylist == null + ? Collections.EMPTY_SET + : Arrays.asList(denylist.split(SEPARATOR))); + } + + /** + * Set internal locale based on given language tag. + */ + public void setLocale(String languageTag) { + mLocale = Locale.forLanguageTag(languageTag); + } + + /** + * Returns the locale from {@code <Set Menu Language>} CEC message. + */ + public Locale getLocale() { + return mLocale; + } + + /** + * Returns whether the locale from {@code <Set Menu Language>} CEC message was already + * denylisted. + */ + public boolean isLocaleDenylisted() { + return mDenylist.contains(mLocale.toLanguageTag()); + } + + /** + * Accepts the new locale and updates system language. + */ + public void acceptLocale() { + mBackgroundExecutor.execute(() -> LocalePicker.updateLocale(mLocale)); + } + + /** + * Declines the locale and puts it on the denylist. + */ + public void declineLocale() { + mDenylist.add(mLocale.toLanguageTag()); + mSecureSettings.putString(Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, + String.join(SEPARATOR, mDenylist)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 701d1391d271..fd2c6dd3ca36 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -106,6 +106,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.UiBackground; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; @@ -138,7 +139,7 @@ import dagger.Lazy; * state of the keyguard, power management events that effect whether the keyguard * should be shown or reset, callbacks to the phone window manager to notify * it of when the keyguard is showing, and events from the keyguard view itself - * stating that the keyguard was succesfully unlocked. + * stating that the keyguard was successfully unlocked. * * Note that the keyguard view is shown when the screen is off (as appropriate) * so that once the screen comes on, it will be ready immediately. @@ -152,15 +153,15 @@ import dagger.Lazy; * - the keyguard is showing * * Example external events that translate to keyguard view changes: - * - screen turned off -> reset the keyguard, and show it so it will be ready + * - screen turned off -> reset the keyguard, and show it, so it will be ready * next time the screen turns on * - keyboard is slid open -> if the keyguard is not secure, hide it * * Events from the keyguard view: - * - user succesfully unlocked keyguard -> hide keyguard view, and no longer + * - user successfully unlocked keyguard -> hide keyguard view, and no longer * restrict input events. * - * Note: in addition to normal power managment events that effect the state of + * Note: in addition to normal power management events that effect the state of * whether the keyguard should be showing, external apps and services may request * that the keyguard be disabled via {@link #setKeyguardEnabled(boolean)}. When * false, this will override all other conditions for turning on the keyguard. @@ -224,7 +225,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, /** * How long we'll wait for the {@link ViewMediatorCallback#keyguardDoneDrawing()} * callback before unblocking a call to {@link #setKeyguardEnabled(boolean)} - * that is reenabling the keyguard. + * that is re-enabling the keyguard. */ private static final int KEYGUARD_DONE_DRAWING_TIMEOUT_MS = 2000; @@ -233,6 +234,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, * keyguard to show even if it is disabled for the current user. */ public static final String OPTION_FORCE_SHOW = "force_show"; + private final DreamOverlayStateController mDreamOverlayStateController; /** The stream type that the lock sounds are tied to. */ private int mUiSoundsStreamType; @@ -273,14 +275,14 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, // these are protected by synchronized (this) /** - * External apps (like the phone app) can tell us to disable the keygaurd. + * External apps (like the phone app) can tell us to disable the keyguard. */ private boolean mExternallyEnabled = true; /** * Remember if an external call to {@link #setKeyguardEnabled} with value * false caused us to hide the keyguard, so that we need to reshow it once - * the keygaurd is reenabled with another call with value true. + * the keyguard is re-enabled with another call with value true. */ private boolean mNeedToReshowWhenReenabled = false; @@ -291,6 +293,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, // AOD is enabled and status bar is in AOD state. private boolean mAodShowing; + // Dream overlay is visible. + private boolean mDreamOverlayShowing; + /** Cached value of #isInputRestricted */ private boolean mInputRestricted; @@ -304,7 +309,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private int mDelayedShowingSequence; /** - * Simiar to {@link #mDelayedProfileShowingSequence}, but it is for profile case. + * Similar to {@link #mDelayedProfileShowingSequence}, but it is for profile case. */ private int mDelayedProfileShowingSequence; @@ -341,7 +346,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private String mPhoneState = TelephonyManager.EXTRA_STATE_IDLE; /** - * Whether a hide is pending an we are just waiting for #startKeyguardExitAnimation to be + * Whether a hide is pending and we are just waiting for #startKeyguardExitAnimation to be * called. * */ private boolean mHiding; @@ -355,7 +360,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); /** - * {@link #setKeyguardEnabled} waits on this condition when it reenables + * {@link #setKeyguardEnabled} waits on this condition when it re-enables * the keyguard. */ private boolean mWaitingUntilKeyguardVisible = false; @@ -470,6 +475,14 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } }; + private final DreamOverlayStateController.Callback mDreamOverlayStateCallback = + new DreamOverlayStateController.Callback() { + @Override + public void onStateChanged() { + mDreamOverlayShowing = mDreamOverlayStateController.isOverlayActive(); + } + }; + KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @Override @@ -494,7 +507,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, synchronized (KeyguardViewMediator.this) { resetKeyguardDonePendingLocked(); if (mLockPatternUtils.isLockScreenDisabled(userId)) { - // If we switching to a user that has keyguard disabled, dismiss keyguard. + // If we are switching to a user that has keyguard disabled, dismiss keyguard. dismiss(null /* callback */, null /* message */); } else { resetStateLocked(); @@ -508,7 +521,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); if (userId != UserHandle.USER_SYSTEM) { UserInfo info = UserManager.get(mContext).getUserInfo(userId); - // Don't try to dismiss if the user has Pin/Patter/Password set + // Don't try to dismiss if the user has Pin/Pattern/Password set if (info == null || mLockPatternUtils.isSecure(userId)) { return; } else if (info.isGuest() || info.isDemo()) { @@ -836,6 +849,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, Lazy<NotificationShadeDepthController> notificationShadeDepthController, ScreenOnCoordinator screenOnCoordinator, InteractionJankMonitor interactionJankMonitor, + DreamOverlayStateController dreamOverlayStateController, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy) { super(context); mFalsingCollector = falsingCollector; @@ -875,6 +889,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy; mScreenOffAnimationController = screenOffAnimationController; mInteractionJankMonitor = interactionJankMonitor; + mDreamOverlayStateController = dreamOverlayStateController; } public void userActivity() { @@ -980,6 +995,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mSystemReady = true; doKeyguardLocked(null); mUpdateMonitor.registerCallback(mUpdateCallback); + mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback); } // Most services aren't available until the system reaches the ready state, so we // send it here when the device first boots. @@ -1041,7 +1057,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mUpdateMonitor.dispatchStartedGoingToSleep(offReason); - // Reset keyguard going away state so we can start listening for fingerprint. We + // Reset keyguard going away state, so we can start listening for fingerprint. We // explicitly DO NOT want to call // mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false) // here, since that will mess with the device lock state. @@ -1121,9 +1137,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } private long getLockTimeout(int userId) { - // if the screen turned off because of timeout or the user hit the power button + // if the screen turned off because of timeout or the user hit the power button, // and we don't need to lock immediately, set an alarm - // to enable it a little bit later (i.e, give the user a chance + // to enable it a bit later (i.e, give the user a chance // to turn the screen back on within a certain window without // having to unlock the screen) final ContentResolver cr = mContext.getContentResolver(); @@ -1217,7 +1233,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } /** - * Let's us know when the device is waking up. + * It will let us know when the device is waking up. */ public void onStartedWakingUp(boolean cameraGestureTriggered) { Trace.beginSection("KeyguardViewMediator#onStartedWakingUp"); @@ -1299,7 +1315,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, if (mExitSecureCallback != null) { if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring"); // we're in the process of handling a request to verify the user - // can get past the keyguard. ignore extraneous requests to disable / reenable + // can get past the keyguard. ignore extraneous requests to disable / re-enable return; } @@ -1310,7 +1326,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, updateInputRestrictedLocked(); hideLocked(); } else if (enabled && mNeedToReshowWhenReenabled) { - // reenabled after previously hidden, reshow + // re-enabled after previously hidden, reshow if (DEBUG) Log.d(TAG, "previously hidden, reshowing, reenabling " + "status bar expansion"); mNeedToReshowWhenReenabled = false; @@ -1328,8 +1344,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } else { showLocked(null); - // block until we know the keygaurd is done drawing (and post a message - // to unblock us after a timeout so we don't risk blocking too long + // block until we know the keyguard is done drawing (and post a message + // to unblock us after a timeout, so we don't risk blocking too long // and causing an ANR). mWaitingUntilKeyguardVisible = true; mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS); @@ -1917,7 +1933,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mExitSecureCallback = null; - // after succesfully exiting securely, no need to reshow + // after successfully exiting securely, no need to reshow // the keyguard when they've released the lock mExternallyEnabled = true; mNeedToReshowWhenReenabled = false; @@ -1992,7 +2008,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return; int id = mLockSounds.play(soundId, - mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/); + mLockSoundVolume, mLockSoundVolume, 1/*priority*/, 0/*loop*/, 1.0f/*rate*/); synchronized (this) { mLockSoundStreamId = id; } @@ -2078,7 +2094,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, || mScreenOnCoordinator.getWakeAndUnlocking() && mWallpaperSupportsAmbientMode) { // When the wallpaper supports ambient mode, the scrim isn't fully opaque during - // wake and unlock and we should fade in the app on top of the wallpaper + // wake and unlock, and we should fade in the app on top of the wallpaper flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; } if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) { @@ -2123,7 +2139,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, Trace.beginSection("KeyguardViewMediator#handleHide"); // It's possible that the device was unlocked in a dream state. It's time to wake up. - if (mAodShowing) { + if (mAodShowing || mDreamOverlayShowing) { PowerManager pm = mContext.getSystemService(PowerManager.class); pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:BOUNCER_DOZING"); @@ -2167,15 +2183,15 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, synchronized (KeyguardViewMediator.this) { // Tell ActivityManager that we canceled the keyguard animation if - // handleStartKeyguardExitAnimation was called but we're not hiding the keyguard, unless - // we're animating the surface behind the keyguard and will be hiding the keyguard - // shortly. + // handleStartKeyguardExitAnimation was called, but we're not hiding the keyguard, + // unless we're animating the surface behind the keyguard and will be hiding the + // keyguard shortly. if (!mHiding && !mSurfaceBehindRemoteAnimationRequested && !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) { if (finishedCallback != null) { // There will not execute animation, send a finish callback to ensure the remote - // animation won't hanging there. + // animation won't hang there. try { finishedCallback.onAnimationFinished(); } catch (RemoteException e) { @@ -2192,7 +2208,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, if (mScreenOnCoordinator.getWakeAndUnlocking()) { // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report - // the next draw from here so we don't have to wait for window manager to signal + // the next draw from here, so we don't have to wait for window manager to signal // this to our ViewRootImpl. mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw(); mScreenOnCoordinator.setWakeAndUnlocking(false); @@ -2344,7 +2360,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } /** - * Called if the keyguard exit animation has been cancelled and we should dismiss to the + * Called if the keyguard exit animation has been cancelled, and we should dismiss to the * keyguard. * * This can happen due to the system cancelling the RemoteAnimation (due to a timeout, a new @@ -2595,7 +2611,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } /** - * Notifies to System UI that the activity behind has now been drawn and it's safe to remove + * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove * the wallpaper and keyguard flag, and WindowManager has started running keyguard exit * animation. * @@ -2609,7 +2625,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } /** - * Notifies to System UI that the activity behind has now been drawn and it's safe to remove + * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove * the wallpaper and keyguard flag, and System UI should start running keyguard exit animation. * * @param apps The list of apps to animate. @@ -2625,7 +2641,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } /** - * Notifies to System UI that the activity behind has now been drawn and it's safe to remove + * Notifies to System UI that the activity behind has now been drawn, and it's safe to remove * the wallpaper and keyguard flag, and start running keyguard exit animation. * * @param startTime the start time of the animation in uptime milliseconds. Deprecated. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index f14d13093620..b49b49cbbb6d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -37,6 +37,7 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.UiBackground; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; @@ -100,6 +101,7 @@ public class KeyguardModule { Lazy<NotificationShadeDepthController> notificationShadeDepthController, ScreenOnCoordinator screenOnCoordinator, InteractionJankMonitor interactionJankMonitor, + DreamOverlayStateController dreamOverlayStateController, Lazy<NotificationShadeWindowController> notificationShadeWindowController) { return new KeyguardViewMediator( context, @@ -125,6 +127,7 @@ public class KeyguardModule { notificationShadeDepthController, screenOnCoordinator, interactionJankMonitor, + dreamOverlayStateController, notificationShadeWindowController ); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index c8cd43287c99..64ebe568c790 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -31,6 +31,7 @@ import androidx.annotation.VisibleForTesting import com.android.systemui.R import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.CrossFadeHelper @@ -82,7 +83,8 @@ class MediaHierarchyManager @Inject constructor( private val notifLockscreenUserManager: NotificationLockscreenUserManager, configurationController: ConfigurationController, wakefulnessLifecycle: WakefulnessLifecycle, - private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + private val dreamOverlayStateController: DreamOverlayStateController ) { /** @@ -167,7 +169,7 @@ class MediaHierarchyManager @Inject constructor( }) } - private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1) + private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1) /** * The last location where this view was at before going to the desired location. This is * useful for guided transitions. @@ -349,6 +351,17 @@ class MediaHierarchyManager @Inject constructor( } /** + * Is the doze animation currently Running + */ + private var dreamOverlayActive: Boolean = false + private set(value) { + if (field != value) { + field = value + updateDesiredLocation(forceNoAnimation = true) + } + } + + /** * The current cross fade progress. 0.5f means it's just switching * between the start and the end location and the content is fully faded, while 0.75f means * that we're halfway faded in again in the target state. @@ -444,6 +457,12 @@ class MediaHierarchyManager @Inject constructor( } }) + dreamOverlayStateController.addCallback(object : DreamOverlayStateController.Callback { + override fun onStateChanged() { + dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it } + } + }) + wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer { override fun onFinishedGoingToSleep() { goingToSleep = false @@ -940,6 +959,7 @@ class MediaHierarchyManager @Inject constructor( statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER)) val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications() val location = when { + dreamOverlayActive -> LOCATION_DREAM_OVERLAY (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS qsExpansion > 0.4f && onLockscreen -> LOCATION_QS !hasActiveMedia -> LOCATION_QS @@ -1035,6 +1055,11 @@ class MediaHierarchyManager @Inject constructor( const val LOCATION_LOCKSCREEN = 2 /** + * Attached on the dream overlay + */ + const val LOCATION_DREAM_OVERLAY = 3 + + /** * Attached at the root of the hierarchy in an overlay */ const val IN_OVERLAY = -1000 diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java index 4baef3aef309..2bc910e4a21a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java +++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java @@ -25,6 +25,7 @@ import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaHierarchyManager; import com.android.systemui.media.MediaHost; import com.android.systemui.media.MediaHostStatesManager; +import com.android.systemui.media.dream.dagger.MediaComplicationComponent; import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper; import com.android.systemui.media.taptotransfer.MediaTttFlags; import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver; @@ -43,11 +44,14 @@ import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; /** Dagger module for the media package. */ -@Module +@Module(subcomponents = { + MediaComplicationComponent.class, +}) public interface MediaModule { String QS_PANEL = "media_qs_panel"; String QUICK_QS_PANEL = "media_quick_qs_panel"; String KEYGUARD = "media_keyguard"; + String DREAM = "dream"; /** */ @Provides @@ -82,6 +86,16 @@ public interface MediaModule { /** */ @Provides @SysUISingleton + @Named(DREAM) + static MediaHost providesDreamMediaHost(MediaHost.MediaHostStateHolder stateHolder, + MediaHierarchyManager hierarchyManager, MediaDataManager dataManager, + MediaHostStatesManager statesManager) { + return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager); + } + + /** */ + @Provides + @SysUISingleton static Optional<MediaTttChipControllerSender> providesMediaTttChipControllerSender( MediaTttFlags mediaTttFlags, Context context, diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java new file mode 100644 index 000000000000..65c5bc76f3c5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dream; + +import static com.android.systemui.media.dagger.MediaModule.DREAM; +import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_CONTAINER; + +import android.widget.FrameLayout; + +import com.android.systemui.media.MediaHierarchyManager; +import com.android.systemui.media.MediaHost; +import com.android.systemui.media.MediaHostState; +import com.android.systemui.util.ViewController; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * {@link MediaComplicationViewController} handles connecting the + * {@link com.android.systemui.dreams.complication.Complication} view to the {@link MediaHost}. + */ +public class MediaComplicationViewController extends ViewController<FrameLayout> { + private final MediaHost mMediaHost; + + @Inject + public MediaComplicationViewController( + @Named(MEDIA_COMPLICATION_CONTAINER) FrameLayout view, + @Named(DREAM) MediaHost mediaHost) { + super(view); + mMediaHost = mediaHost; + } + + @Override + protected void onInit() { + super.onInit(); + mMediaHost.setExpansion(MediaHostState.COLLAPSED); + mMediaHost.setShowsOnlyActiveMedia(true); + mMediaHost.setFalsingProtectionNeeded(true); + mMediaHost.init(MediaHierarchyManager.LOCATION_DREAM_OVERLAY); + } + + @Override + protected void onViewAttached() { + mMediaHost.hostView.setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT)); + mView.addView(mMediaHost.hostView); + } + + @Override + protected void onViewDetached() { + mView.removeView(mMediaHost.hostView); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java new file mode 100644 index 000000000000..2c35db337cda --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dream; + +import com.android.systemui.dreams.complication.Complication; +import com.android.systemui.dreams.complication.ComplicationViewModel; +import com.android.systemui.media.dream.dagger.MediaComplicationComponent; + +import javax.inject.Inject; + +/** + * Media control complication for dream overlay. + */ +public class MediaDreamComplication implements Complication { + MediaComplicationComponent.Factory mComponentFactory; + + /** + * Default constructor for {@link MediaDreamComplication}. + */ + @Inject + public MediaDreamComplication(MediaComplicationComponent.Factory componentFactory) { + mComponentFactory = componentFactory; + } + + @Override + public ViewHolder createView(ComplicationViewModel model) { + return mComponentFactory.create().getViewHolder(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java new file mode 100644 index 000000000000..8934cd1085b8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dream; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.CoreStartable; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.media.MediaData; +import com.android.systemui.media.MediaDataManager; +import com.android.systemui.media.SmartspaceMediaData; + +import javax.inject.Inject; + +/** + * {@link MediaDreamSentinel} is responsible for tracking media state and registering/unregistering + * the media complication as appropriate + */ +public class MediaDreamSentinel extends CoreStartable { + private MediaDataManager.Listener mListener = new MediaDataManager.Listener() { + private boolean mAdded; + @Override + public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) { + } + + @Override + public void onMediaDataRemoved(@NonNull String key) { + if (!mAdded) { + return; + } + + if (mMediaDataManager.hasActiveMedia()) { + return; + } + + mAdded = false; + mDreamOverlayStateController.removeComplication(mComplication); + } + + @Override + public void onSmartspaceMediaDataLoaded(@NonNull String key, + @NonNull SmartspaceMediaData data, boolean shouldPrioritize, + boolean isSsReactivated) { + } + + @Override + public void onMediaDataLoaded(@NonNull String key, @Nullable String oldKey, + @NonNull MediaData data, boolean immediately, int receivedSmartspaceCardLatency) { + if (mAdded) { + return; + } + + if (!mMediaDataManager.hasActiveMedia()) { + return; + } + + mAdded = true; + mDreamOverlayStateController.addComplication(mComplication); + } + }; + + private final MediaDataManager mMediaDataManager; + private final DreamOverlayStateController mDreamOverlayStateController; + private final MediaDreamComplication mComplication; + + @Inject + public MediaDreamSentinel(Context context, MediaDataManager mediaDataManager, + DreamOverlayStateController dreamOverlayStateController, + MediaDreamComplication complication) { + super(context); + mMediaDataManager = mediaDataManager; + mDreamOverlayStateController = dreamOverlayStateController; + mComplication = complication; + } + + @Override + public void start() { + mMediaDataManager.addListener(mListener); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java new file mode 100644 index 000000000000..128a38c639be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaViewHolder.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dream; + +import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_CONTAINER; +import static com.android.systemui.media.dream.dagger.MediaComplicationComponent.MediaComplicationModule.MEDIA_COMPLICATION_LAYOUT_PARAMS; + +import android.view.View; +import android.widget.FrameLayout; + +import com.android.systemui.dreams.complication.Complication; +import com.android.systemui.dreams.complication.ComplicationLayoutParams; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * {@link Complication.ViewHolder} implementation for media control. + */ +public class MediaViewHolder implements Complication.ViewHolder { + private final FrameLayout mContainer; + private final MediaComplicationViewController mViewController; + private final ComplicationLayoutParams mLayoutParams; + + @Inject + MediaViewHolder(@Named(MEDIA_COMPLICATION_CONTAINER) FrameLayout container, + MediaComplicationViewController controller, + @Named(MEDIA_COMPLICATION_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams) { + mContainer = container; + mViewController = controller; + mViewController.init(); + mLayoutParams = layoutParams; + } + + @Override + public View getView() { + return mContainer; + } + + @Override + public ComplicationLayoutParams getLayoutParams() { + return mLayoutParams; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java new file mode 100644 index 000000000000..3372899b8fd7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dream/dagger/MediaComplicationComponent.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dream.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.content.Context; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.android.systemui.dreams.complication.ComplicationLayoutParams; +import com.android.systemui.media.dream.MediaViewHolder; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Named; +import javax.inject.Scope; + +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * {@link MediaComplicationComponent} is responsible for generating dependencies surrounding the + * media {@link com.android.systemui.dreams.complication.Complication}, such as view controllers + * and layout details. + */ +@Subcomponent(modules = { + MediaComplicationComponent.MediaComplicationModule.class, +}) +@MediaComplicationComponent.MediaComplicationScope +public interface MediaComplicationComponent { + @Documented + @Retention(RUNTIME) + @Scope + @interface MediaComplicationScope {} + + /** + * Generates {@link MediaComplicationComponent}. + */ + @Subcomponent.Factory + interface Factory { + MediaComplicationComponent create(); + } + + /** + * Creates {@link MediaViewHolder}. + */ + MediaViewHolder getViewHolder(); + + /** + * Scoped values for {@link MediaComplicationComponent}. + */ + @Module + interface MediaComplicationModule { + String MEDIA_COMPLICATION_CONTAINER = "media_complication_container"; + String MEDIA_COMPLICATION_LAYOUT_PARAMS = "media_complication_layout_params"; + + /** + * Provides the complication view. + */ + @Provides + @MediaComplicationScope + @Named(MEDIA_COMPLICATION_CONTAINER) + static FrameLayout provideComplicationContainer(Context context) { + return new FrameLayout(context); + } + + /** + * Provides the layout parameters for the complication view. + */ + @Provides + @MediaComplicationScope + @Named(MEDIA_COMPLICATION_LAYOUT_PARAMS) + static ComplicationLayoutParams provideLayoutParams() { + return new ComplicationLayoutParams(0, + ViewGroup.LayoutParams.WRAP_CONTENT, + ComplicationLayoutParams.POSITION_BOTTOM + | ComplicationLayoutParams.POSITION_END, + ComplicationLayoutParams.DIRECTION_UP, + 0, + true); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 4f4bd1e86e7c..be45a62eeba6 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -104,7 +104,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private static final int MAX_NUM_LOGGED_PREDICTIONS = 10; private static final int MAX_NUM_LOGGED_GESTURES = 10; - static final boolean DEBUG_MISSING_GESTURE = false; + // Temporary log until b/202433017 is resolved + static final boolean DEBUG_MISSING_GESTURE = true; static final String DEBUG_MISSING_GESTURE_TAG = "NoBackGesture"; private ISystemGestureExclusionListener mGestureExclusionListener = @@ -318,7 +319,11 @@ public class EdgeBackGestureHandler extends CurrentUserTracker String recentsPackageName = recentsComponentName.getPackageName(); PackageManager manager = context.getPackageManager(); try { - Resources resources = manager.getResourcesForApplication(recentsPackageName); + Resources resources = manager.getResourcesForApplication( + manager.getApplicationInfo(recentsPackageName, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.GET_SHARED_LIBRARY_FILES)); int resId = resources.getIdentifier( "gesture_blocking_activities", "array", recentsPackageName); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 597e4242af66..9d43d303b834 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -37,6 +37,7 @@ import android.content.Context; import android.graphics.drawable.Icon; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; +import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; @@ -154,6 +155,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT; private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT; private static final int MSG_TILE_SERVICE_REQUEST_CANCEL = 62 << MSG_SHIFT; + private static final int MSG_SET_BIOMETRICS_LISTENER = 63 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -317,6 +319,12 @@ public class CommandQueue extends IStatusBar.Stub implements } /** + * @see IStatusBar#setBiometicContextListener(IBiometricContextListener) + */ + default void setBiometicContextListener(IBiometricContextListener listener) { + } + + /** * @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener) */ default void setUdfpsHbmListener(IUdfpsHbmListener listener) { @@ -958,6 +966,13 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override + public void setBiometicContextListener(IBiometricContextListener listener) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_SET_BIOMETRICS_LISTENER, listener).sendToTarget(); + } + } + + @Override public void setUdfpsHbmListener(IUdfpsHbmListener listener) { synchronized (mLock) { mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget(); @@ -1411,6 +1426,12 @@ public class CommandQueue extends IStatusBar.Stub implements mCallbacks.get(i).hideAuthenticationDialog(); } break; + case MSG_SET_BIOMETRICS_LISTENER: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).setBiometicContextListener( + (IBiometricContextListener) msg.obj); + } + break; case MSG_SET_UDFPS_HBM_LISTENER: for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index c136d9cc7272..b312ce20b313 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -337,11 +337,11 @@ class LockscreenShadeTransitionController @Inject constructor( if (field != value || forceApplyAmount) { field = value if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) { - nsslController.setTransitionToFullShadeAmount(field) - notificationPanelController.setTransitionToFullShadeAmount(field, - false /* animate */, 0 /* delay */) qSDragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance) + nsslController.setTransitionToFullShadeAmount(field, qSDragProgress) qS.setTransitionToFullShadeAmount(field, qSDragProgress) + notificationPanelController.setTransitionToFullShadeAmount(field, + false /* animate */, 0 /* delay */) // TODO: appear media also in split shade val mediaAmount = if (useSplitShade) 0f else field mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 51a66aad39fb..3411eab23d63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar; +import static com.android.systemui.statusbar.phone.NotificationIconContainer.MAX_ICONS_ON_LOCKSCREEN; + import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -32,6 +34,7 @@ import android.view.animation.PathInterpolator; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.R; +import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -45,6 +48,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.statusbar.phone.NotificationIconContainer; +import com.android.systemui.util.Utils; /** * A notification shelf view that is placed inside the notification scroller. It manages the @@ -81,6 +85,11 @@ public class NotificationShelf extends ActivatableNotificationView implements private int mIndexOfFirstViewInShelf = -1; private float mCornerAnimationDistance; private NotificationShelfController mController; + private int mActualWidth = -1; + private boolean mUseSplitShade; + + /** Fraction of lockscreen to shade animation (on lockscreen swipe down). */ + private float mFractionToShade; public NotificationShelf(Context context, AttributeSet attrs) { super(context, attrs); @@ -122,13 +131,16 @@ public class NotificationShelf extends ActivatableNotificationView implements layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height); setLayoutParams(layoutParams); - int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding); + final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding); mShelfIcons.setPadding(padding, 0, padding, 0); mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold); mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf); mCornerAnimationDistance = res.getDimensionPixelSize( R.dimen.notification_corner_animation_distance); + // TODO(b/213480466) enable short shelf on split shade + mUseSplitShade = Utils.shouldUseSplitNotificationShade(mContext.getResources()); + mShelfIcons.setInNotificationIconShelf(true); if (!mShowNotificationShelf) { setVisibility(GONE); @@ -203,6 +215,10 @@ public class NotificationShelf extends ActivatableNotificationView implements final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight(); viewState.yTranslation = stackEnd - viewState.height; + + final int shortestWidth = mShelfIcons.calculateWidthFor(MAX_ICONS_ON_LOCKSCREEN); + final float fraction = Interpolators.STANDARD.getInterpolation(mFractionToShade); + updateStateWidth(viewState, fraction, shortestWidth); } else { viewState.hidden = true; viewState.location = ExpandableViewState.LOCATION_GONE; @@ -211,6 +227,77 @@ public class NotificationShelf extends ActivatableNotificationView implements } /** + * @param shelfState View state for NotificationShelf + * @param fraction Fraction of lockscreen to shade transition + * @param shortestWidth Shortest width to use for lockscreen shelf + */ + @VisibleForTesting + public void updateStateWidth(ShelfState shelfState, float fraction, int shortestWidth) { + shelfState.actualWidth = !mUseSplitShade && mAmbientState.isOnKeyguard() + ? (int) MathUtils.lerp(shortestWidth, getWidth(), fraction) + : getWidth(); + } + + /** + * @param fractionToShade Fraction of lockscreen to shade transition + */ + public void setFractionToShade(float fractionToShade) { + mFractionToShade = fractionToShade; + } + + /** + * @return Actual width of shelf, accounting for possible ongoing width animation + */ + public int getActualWidth() { + return mActualWidth > -1 ? mActualWidth : getWidth(); + } + + /** + * @param localX Click x from left of screen + * @param slop Margin of error within which we count x for valid click + * @param left Left of shelf, from left of screen + * @param right Right of shelf, from left of screen + * @return Whether click x was in view + */ + @VisibleForTesting + public boolean isXInView(float localX, float slop, float left, float right) { + return (left - slop) <= localX && localX < (right + slop); + } + + /** + * @param localY Click y from top of shelf + * @param slop Margin of error within which we count y for valid click + * @param top Top of shelf + * @param bottom Height of shelf + * @return Whether click y was in view + */ + @VisibleForTesting + public boolean isYInView(float localY, float slop, float top, float bottom) { + return (top - slop) <= localY && localY < (bottom + slop); + } + + /** + * @param localX Click x + * @param localY Click y + * @param slop Margin of error for valid click + * @return Whether this click was on the visible (non-clipped) part of the shelf + */ + @Override + public boolean pointInView(float localX, float localY, float slop) { + final float containerWidth = getWidth(); + final float shelfWidth = getActualWidth(); + + final float left = isLayoutRtl() ? containerWidth - shelfWidth : 0; + final float right = isLayoutRtl() ? containerWidth : shelfWidth; + + final float top = mClipTopAmount; + final float bottom = getActualHeight(); + + return isXInView(localX, slop, left, right) + && isYInView(localY, slop, top, bottom); + } + + /** * Update the shelf appearance based on the other notifications around it. This transforms * the icons from the notification area into the shelf. */ @@ -732,11 +819,15 @@ public class NotificationShelf extends ActivatableNotificationView implements // we always want to clip to our sides, such that nothing can draw outside of these bounds int height = getResources().getDisplayMetrics().heightPixels; mClipRect.set(0, -height, getWidth(), height); - mShelfIcons.setClipBounds(mClipRect); + if (mShelfIcons != null) { + mShelfIcons.setClipBounds(mClipRect); + } } private void updateRelativeOffset() { - mCollapsedIcons.getLocationOnScreen(mTmp); + if (mCollapsedIcons != null) { + mCollapsedIcons.getLocationOnScreen(mTmp); + } getLocationOnScreen(mTmp); } @@ -831,9 +922,20 @@ public class NotificationShelf extends ActivatableNotificationView implements mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf); } - private class ShelfState extends ExpandableViewState { + public class ShelfState extends ExpandableViewState { private boolean hasItemsInStableShelf; private ExpandableView firstViewInShelf; + public int actualWidth = -1; + + private void updateShelfWidth(View view) { + if (actualWidth < 0) { + return; + } + mActualWidth = actualWidth; + ActivatableNotificationView anv = (ActivatableNotificationView) view; + anv.getBackgroundNormal().setActualWidth(actualWidth); + mShelfIcons.setActualLayoutWidth(actualWidth); + } @Override public void applyToView(View view) { @@ -846,19 +948,21 @@ public class NotificationShelf extends ActivatableNotificationView implements updateAppearance(); setHasItemsInStableShelf(hasItemsInStableShelf); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); + updateShelfWidth(view); } @Override - public void animateTo(View child, AnimationProperties properties) { + public void animateTo(View view, AnimationProperties properties) { if (!mShowNotificationShelf) { return; } - super.animateTo(child, properties); + super.animateTo(view, properties); setIndexOfFirstViewInShelf(firstViewInShelf); updateAppearance(); setHasItemsInStableShelf(hasItemsInStableShelf); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); + updateShelfWidth(view); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index abfdfaf2115f..391525e11866 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -27,6 +27,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.Assert; import com.android.wm.shell.bubbles.Bubbles; @@ -98,6 +100,8 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle private final ForegroundServiceSectionController mFgsSectionController; private final NotifPipelineFlags mNotifPipelineFlags; private AssistantFeedbackController mAssistantFeedbackController; + private final KeyguardStateController mKeyguardStateController; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final Context mContext; private NotificationPresenter mPresenter; @@ -129,7 +133,9 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle DynamicChildBindController dynamicChildBindController, LowPriorityInflationHelper lowPriorityInflationHelper, AssistantFeedbackController assistantFeedbackController, - NotifPipelineFlags notifPipelineFlags) { + NotifPipelineFlags notifPipelineFlags, + KeyguardUpdateMonitor keyguardUpdateMonitor, + KeyguardStateController keyguardStateController) { mContext = context; mHandler = mainHandler; mFeatureFlags = featureFlags; @@ -149,6 +155,8 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle mDynamicChildBindController = dynamicChildBindController; mLowPriorityInflationHelper = lowPriorityInflationHelper; mAssistantFeedbackController = assistantFeedbackController; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mKeyguardStateController = keyguardStateController; } public void setUpWithPresenter(NotificationPresenter presenter, @@ -174,6 +182,11 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle beginUpdate(); + boolean dynamicallyUnlocked = mDynamicPrivacyController.isDynamicallyUnlocked() + && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD + && mKeyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing( + KeyguardUpdateMonitor.getCurrentUser())) + && !mKeyguardStateController.isKeyguardGoingAway(); List<NotificationEntry> activeNotifications = mEntryManager.getVisibleNotifications(); ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size()); final int N = activeNotifications.size(); @@ -192,7 +205,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode(currentUserId); boolean userPublic = devicePublic || mLockscreenUserManager.isLockscreenPublicMode(userId); - if (userPublic && mDynamicPrivacyController.isDynamicallyUnlocked() + if (userPublic && dynamicallyUnlocked && (userId == currentUserId || userId == UserHandle.USER_ALL || !mLockscreenUserManager.needsSeparateWorkChallenge(userId))) { userPublic = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index bd948eceab9c..f56602ee2bcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -518,6 +518,7 @@ public class StatusBarStateControllerImpl implements } private void recordHistoricalState(int newState, int lastState, boolean upcoming) { + Trace.traceCounter(Trace.TRACE_TAG_APP, "statusBarState", newState); mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; HistoricalState state = mHistoricalRecords[mHistoryIndex]; state.mNewState = newState; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index 6c3a9093fa98..c74621df94c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java @@ -16,13 +16,19 @@ package com.android.systemui.statusbar; -import android.content.Context; -import android.os.AsyncTask; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.AudioAttributes; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.Executor; import javax.inject.Inject; @@ -32,21 +38,75 @@ import javax.inject.Inject; public class VibratorHelper { private final Vibrator mVibrator; - private final Context mContext; private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); + private final Executor mExecutor; /** */ @Inject - public VibratorHelper(Context context) { - mContext = context; - mVibrator = context.getSystemService(Vibrator.class); + public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) { + mExecutor = executor; + mVibrator = vibrator; } + /** + * @see Vibrator#vibrate(long) + */ public void vibrate(final int effectId) { - AsyncTask.execute(() -> + if (!hasVibrator()) { + return; + } + mExecutor.execute(() -> mVibrator.vibrate(VibrationEffect.get(effectId, false /* fallback */), TOUCH_VIBRATION_ATTRIBUTES)); } + + /** + * @see Vibrator#vibrate(int, String, VibrationEffect, String, VibrationAttributes) + */ + public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, + String reason, @NonNull VibrationAttributes attributes) { + if (!hasVibrator()) { + return; + } + mExecutor.execute(() -> mVibrator.vibrate(uid, opPkg, vibe, reason, attributes)); + } + + /** + * @see Vibrator#vibrate(VibrationEffect, AudioAttributes) + */ + public void vibrate(@NonNull VibrationEffect effect, @NonNull AudioAttributes attributes) { + if (!hasVibrator()) { + return; + } + mExecutor.execute(() -> mVibrator.vibrate(effect, attributes)); + } + + /** + * @see Vibrator#vibrate(VibrationEffect) + */ + public void vibrate(@NotNull VibrationEffect effect) { + if (!hasVibrator()) { + return; + } + mExecutor.execute(() -> mVibrator.vibrate(effect)); + } + + /** + * @see Vibrator#hasVibrator() + */ + public boolean hasVibrator() { + return mVibrator != null && mVibrator.hasVibrator(); + } + + /** + * @see Vibrator#cancel() + */ + public void cancel() { + if (!hasVibrator()) { + return; + } + mExecutor.execute(mVibrator::cancel); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index d574cdabced6..e3d0d9802b8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -23,6 +23,7 @@ import android.os.Handler; import android.service.dreams.IDreamManager; import com.android.internal.statusbar.IStatusBarService; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.SysUISingleton; @@ -72,6 +73,7 @@ import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.tracing.ProtoTracer; @@ -214,7 +216,9 @@ public interface StatusBarDependenciesModule { DynamicChildBindController dynamicChildBindController, LowPriorityInflationHelper lowPriorityInflationHelper, AssistantFeedbackController assistantFeedbackController, - NotifPipelineFlags notifPipelineFlags) { + NotifPipelineFlags notifPipelineFlags, + KeyguardUpdateMonitor keyguardUpdateMonitor, + KeyguardStateController keyguardStateController) { return new NotificationViewHierarchyManager( context, mainHandler, @@ -231,7 +235,9 @@ public interface StatusBarDependenciesModule { dynamicChildBindController, lowPriorityInflationHelper, assistantFeedbackController, - notifPipelineFlags); + notifPipelineFlags, + keyguardUpdateMonitor, + keyguardStateController); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java index 0ce07cb99d52..22300d8c180d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java @@ -98,6 +98,8 @@ public class KeyguardCoordinator implements Coordinator { setupInvalidateNotifListCallbacks(); // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator pipeline.addFinalizeFilter(mNotifFilter); + + updateSectionHeadersVisibility(); } private final NotifFilter mNotifFilter = new NotifFilter(TAG) { @@ -164,6 +166,8 @@ public class KeyguardCoordinator implements Coordinator { } } + // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on + // these same updates private void setupInvalidateNotifListCallbacks() { // register onKeyguardShowing callback mKeyguardStateController.addCallback(mKeyguardCallback); @@ -220,10 +224,7 @@ public class KeyguardCoordinator implements Coordinator { } private void invalidateListFromFilter(String reason) { - boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD; - boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders(); - boolean showSections = !onKeyguard && !neverShowSections; - mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections); + updateSectionHeadersVisibility(); mNotifFilter.invalidateList(); } @@ -235,6 +236,13 @@ public class KeyguardCoordinator implements Coordinator { 1) == 0; } + private void updateSectionHeadersVisibility() { + boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD; + boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders(); + boolean showSections = !onKeyguard && !neverShowSections; + mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections); + } + private final KeyguardStateController.Callback mKeyguardCallback = new KeyguardStateController.Callback() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt index a115e0400de3..9c82cb64a0d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt @@ -17,7 +17,10 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.os.UserHandle +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.DynamicPrivacyController import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry @@ -26,6 +29,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator +import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Module import dagger.Provides @@ -36,9 +40,13 @@ object SensitiveContentCoordinatorModule { @CoordinatorScope fun provideCoordinator( dynamicPrivacyController: DynamicPrivacyController, - lockscreenUserManager: NotificationLockscreenUserManager + lockscreenUserManager: NotificationLockscreenUserManager, + keyguardUpdateMonitor: KeyguardUpdateMonitor, + statusBarStateController: StatusBarStateController, + keyguardStateController: KeyguardStateController ): SensitiveContentCoordinator = - SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager) + SensitiveContentCoordinatorImpl(dynamicPrivacyController, lockscreenUserManager, + keyguardUpdateMonitor, statusBarStateController, keyguardStateController) } /** Coordinates re-inflation and post-processing of sensitive notification content. */ @@ -46,7 +54,10 @@ interface SensitiveContentCoordinator : Coordinator private class SensitiveContentCoordinatorImpl( private val dynamicPrivacyController: DynamicPrivacyController, - private val lockscreenUserManager: NotificationLockscreenUserManager + private val lockscreenUserManager: NotificationLockscreenUserManager, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val statusBarStateController: StatusBarStateController, + private val keyguardStateController: KeyguardStateController ) : Invalidator("SensitiveContentInvalidator"), SensitiveContentCoordinator, DynamicPrivacyController.Listener, @@ -61,6 +72,19 @@ private class SensitiveContentCoordinatorImpl( override fun onDynamicPrivacyChanged(): Unit = invalidateList() override fun onBeforeRenderList(entries: List<ListEntry>) { + if (keyguardStateController.isKeyguardGoingAway() || + statusBarStateController.getState() == StatusBarState.KEYGUARD && + keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing( + KeyguardUpdateMonitor.getCurrentUser())) { + // don't update yet if: + // - the keyguard is currently going away + // - LS is about to be dismissed by a biometric that bypasses LS (avoid notif flash) + + // TODO(b/206118999): merge this class with KeyguardCoordinator which ensures the + // dependent state changes invalidate the pipeline + return + } + val currentUserId = lockscreenUserManager.currentUserId val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId) val deviceSensitive = devicePublic && diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt index 28cd28594c3e..386e2d31380c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt @@ -104,19 +104,14 @@ class ShadeViewDiffer( views.remove(childNode.controller.view) } - if (childCompletelyRemoved && parentSpec == null) { - // If both the child and the parent are being removed at the same time, then - // keep the child attached to the parent for animation purposes - logger.logSkippingDetach(childNode.label, parentNode.label) - } else { - logger.logDetachingChild( - childNode.label, - !childCompletelyRemoved, - parentNode.label, - newParentNode?.label) - parentNode.removeChild(childNode, !childCompletelyRemoved) - childNode.parent = null - } + logger.logDetachingChild( + key = childNode.label, + isTransfer = !childCompletelyRemoved, + isParentRemoved = parentSpec == null, + oldParent = parentNode.label, + newParent = newParentNode?.label) + parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved) + childNode.parent = null } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt index d27455004c01..4c0357243d48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt @@ -28,25 +28,18 @@ class ShadeViewDifferLogger @Inject constructor( fun logDetachingChild( key: String, isTransfer: Boolean, + isParentRemoved: Boolean, oldParent: String?, newParent: String? ) { buffer.log(TAG, LogLevel.DEBUG, { str1 = key bool1 = isTransfer + bool2 = isParentRemoved str2 = oldParent str3 = newParent }, { - "Detach $str1 isTransfer=$bool1 oldParent=$str2 newParent=$str3" - }) - } - - fun logSkippingDetach(key: String, parent: String?) { - buffer.log(TAG, LogLevel.DEBUG, { - str1 = key - str2 = parent - }, { - "Skipping detach of $str1 because its parent $str2 is also being detached" + "Detach $str1 isTransfer=$bool1 isParentRemoved=$bool2 oldParent=$str2 newParent=$str3" }) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 5d6d0f701f12..fca2aa167e37 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -162,6 +162,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView updateBackgroundTint(); } + /** + * @return The background of this view. + */ + public NotificationBackgroundView getBackgroundNormal() { + return mBackgroundNormal; + } + @Override protected void onFinishInflate() { super.onFinishInflate(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index dbd22db333ff..1f7d93012e39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -49,6 +49,7 @@ import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.Trace; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.util.ArraySet; @@ -1246,6 +1247,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } private void reInflateViews() { + Trace.beginSection("ExpandableNotificationRow#reInflateViews"); // Let's update our childrencontainer. This is intentionally not guarded with // mIsSummaryWithChildren since we might have had children but not anymore. if (mChildrenContainer != null) { @@ -1277,6 +1279,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry); params.setNeedsReinflation(true); mRowContentBindStage.requestRebind(mEntry, null /* callback */); + Trace.endSection(); } @Override @@ -1737,6 +1740,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure")); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + Trace.endSection(); + } + + /** Generates and appends "(MessagingStyle)" type tag to passed string for tracing. */ + @NonNull + private String appendTraceStyleTag(@NonNull String traceTag) { + if (!Trace.isEnabled()) { + return traceTag; + } + + Class<? extends Notification.Style> style = + getEntry().getSbn().getNotification().getNotificationStyle(); + if (style == null) { + return traceTag + "(nostyle)"; + } else { + return traceTag + "(" + style.getSimpleName() + ")"; + } + } + + @Override protected void onFinishInflate() { super.onFinishInflate(); mPublicLayout = findViewById(R.id.expandedPublic); @@ -2542,6 +2568,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + Trace.beginSection(appendTraceStyleTag("ExpNotRow#onLayout")); int intrinsicBefore = getIntrinsicHeight(); super.onLayout(changed, left, top, right, bottom); if (intrinsicBefore != getIntrinsicHeight() @@ -2555,6 +2582,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mLayoutListener != null) { mLayoutListener.onLayout(); } + Trace.endSection(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index 0f615aa9356f..c640ab6c3a90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -29,6 +29,7 @@ import android.view.View; import com.android.internal.util.ArrayUtils; import com.android.systemui.R; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.ExpandAnimationParameters; /** @@ -39,15 +40,17 @@ public class NotificationBackgroundView extends View { private final boolean mDontModifyCorners; private Drawable mBackground; private int mClipTopAmount; - private int mActualHeight; private int mClipBottomAmount; private int mTintColor; private final float[] mCornerRadii = new float[8]; private boolean mBottomIsRounded; private int mBackgroundTop; private boolean mBottomAmountClips = true; + private int mActualHeight = -1; + private int mActualWidth = -1; private boolean mExpandAnimationRunning; - private float mActualWidth; + private int mExpandAnimationWidth = -1; + private int mExpandAnimationHeight = -1; private int mDrawableAlpha = 255; private boolean mIsPressedAllowed; @@ -59,11 +62,12 @@ public class NotificationBackgroundView extends View { @Override protected void onDraw(Canvas canvas) { - if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop + if (mClipTopAmount + mClipBottomAmount < getActualHeight() - mBackgroundTop || mExpandAnimationRunning) { canvas.save(); if (!mExpandAnimationRunning) { - canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount); + canvas.clipRect(0, mClipTopAmount, getWidth(), + getActualHeight() - mClipBottomAmount); } draw(canvas, mBackground); canvas.restore(); @@ -73,17 +77,23 @@ public class NotificationBackgroundView extends View { private void draw(Canvas canvas, Drawable drawable) { if (drawable != null) { int top = mBackgroundTop; - int bottom = mActualHeight; + int bottom = getActualHeight(); if (mBottomIsRounded && mBottomAmountClips && !mExpandAnimationRunning) { bottom -= mClipBottomAmount; } - int left = 0; - int right = getWidth(); + final boolean isRtl = isLayoutRtl(); + final int width = getWidth(); + final int actualWidth = getActualWidth(); + + int left = isRtl ? width - actualWidth : 0; + int right = isRtl ? width : actualWidth; + if (mExpandAnimationRunning) { - left = (int) ((getWidth() - mActualWidth) / 2.0f); - right = (int) (left + mActualWidth); + // Horizontally center this background view inside of the container + left = (int) ((width - actualWidth) / 2.0f); + right = (int) (left + actualWidth); } drawable.setBounds(left, top, right, bottom); drawable.draw(canvas); @@ -152,8 +162,26 @@ public class NotificationBackgroundView extends View { invalidate(); } - public int getActualHeight() { - return mActualHeight; + private int getActualHeight() { + if (mExpandAnimationRunning && mExpandAnimationHeight > -1) { + return mExpandAnimationHeight; + } else if (mActualHeight > -1) { + return mActualHeight; + } + return getHeight(); + } + + public void setActualWidth(int actualWidth) { + mActualWidth = actualWidth; + } + + private int getActualWidth() { + if (mExpandAnimationRunning && mExpandAnimationWidth > -1) { + return mExpandAnimationWidth; + } else if (mActualWidth > -1) { + return mActualWidth; + } + return getWidth(); } public void setClipTopAmount(int clipTopAmount) { @@ -241,9 +269,9 @@ public class NotificationBackgroundView extends View { } /** Set the current expand animation size. */ - public void setExpandAnimationSize(int actualWidth, int actualHeight) { - mActualHeight = actualHeight; - mActualWidth = actualWidth; + public void setExpandAnimationSize(int width, int height) { + mExpandAnimationHeight = width; + mExpandAnimationWidth = height; invalidate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 9655a9ce5e45..dd72615768b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -199,7 +199,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private int mMaxTopPadding; private int mTopPadding; private boolean mAnimateNextTopPaddingChange; - private int mBottomMargin; + private int mBottomPadding; private int mBottomInset = 0; private float mQsExpansionFraction; private final int mSplitShadeMinContentHeight; @@ -981,7 +981,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mMinTopOverScrollToEscape = res.getDimensionPixelSize( R.dimen.min_top_overscroll_to_qs); mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext); - mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom); + mBottomPadding = res.getDimensionPixelSize(R.dimen.notification_panel_padding_bottom); mMinimumPaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings); mQsTilePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal); mSkinnyNotifsInLandscape = res.getBoolean(R.bool.config_skinnyNotifsInLandscape); @@ -2280,7 +2280,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // The topPadding can be bigger than the regular padding when qs is expanded, in that // state the maxPanelHeight and the contentHeight should be bigger - mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin; + mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomPadding; updateScrollability(); clampScrollPosition(); updateStackPosition(); @@ -5497,6 +5497,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } /** + * @param fraction Fraction of the lockscreen to shade transition. 0f for all other states. + * Once the lockscreen to shade transition completes and the shade is 100% open + * LockscreenShadeTransitionController resets fraction to 0 + * where it remains until the next lockscreen-to-shade transition. + */ + public void setFractionToShade(float fraction) { + mShelf.setFractionToShade(fraction); + requestChildrenUpdate(); + } + + /** * Set a listener to when scrolling changes. */ public void setOnScrollListener(Consumer<Integer> listener) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 0d0e5e850523..334128a2b4ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1515,10 +1515,18 @@ public class NotificationStackScrollLayoutController { } /** - * Set the amount of pixels we have currently dragged down if we're transitioning to the full - * shade. 0.0f means we're not transitioning yet. + * @param amount The amount of pixels we have currently dragged down + * for the lockscreen to shade transition. 0f for all other states. + * @param fraction The fraction of lockscreen to shade transition. + * 0f for all other states. + * + * Once the lockscreen to shade transition completes and the shade is 100% open, + * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain + * until the next lockscreen-to-shade transition. */ - public void setTransitionToFullShadeAmount(float amount) { + public void setTransitionToFullShadeAmount(float amount, float fraction) { + mView.setFractionToShade(fraction); + float extraTopInset = 0.0f; if (mStatusBarStateController.getState() == KEYGUARD) { float overallProgress = MathUtils.saturate(amount / mView.getHeight()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 8f0579cc4693..e24cd3e9b016 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -46,7 +46,7 @@ import java.util.List; */ public class StackScrollAlgorithm { - public static final float START_FRACTION = 0.3f; + public static final float START_FRACTION = 0.5f; private static final String LOG_TAG = "StackScrollAlgorithm"; private final ViewGroup mHostView; @@ -61,7 +61,7 @@ public class StackScrollAlgorithm { @VisibleForTesting float mHeadsUpInset; private int mPinnedZTranslationExtra; private float mNotificationScrimPadding; - private int mCloseHandleUnderlapHeight; + private int mMarginBottom; public StackScrollAlgorithm( Context context, @@ -87,7 +87,7 @@ public class StackScrollAlgorithm { R.dimen.heads_up_pinned_elevation); mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings); - mCloseHandleUnderlapHeight = res.getDimensionPixelSize(R.dimen.close_handle_underlap); + mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom); } /** @@ -463,7 +463,7 @@ public class StackScrollAlgorithm { } } else { if (view instanceof EmptyShadeView) { - float fullHeight = ambientState.getLayoutMaxHeight() + mCloseHandleUnderlapHeight + float fullHeight = ambientState.getLayoutMaxHeight() + mMarginBottom - ambientState.getStackY(); viewState.yTranslation = (fullHeight - getMaxAllowedChildHeight(view)) / 2f; } else if (view != ambientState.getTrackedHeadsUpRow()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 0d2bddcc8b77..7c9df426aba6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -44,6 +44,7 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_STANDARD = 360; public static final int ANIMATION_DURATION_CORNER_RADIUS = 200; public static final int ANIMATION_DURATION_WAKEUP = 500; + public static final int ANIMATION_DURATION_WAKEUP_SCRIM = 667; public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448; public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; public static final int ANIMATION_DURATION_SWIPE = 200; @@ -343,9 +344,11 @@ public class StackStateAnimator { for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { final ExpandableView changingView = (ExpandableView) event.mChangingView; boolean loggable = false; + boolean isHeadsUp = false; String key = null; if (changingView instanceof ExpandableNotificationRow && mLogger != null) { loggable = true; + isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp(); key = ((ExpandableNotificationRow) changingView).getEntry().getKey(); } if (event.animationType == @@ -357,6 +360,9 @@ public class StackStateAnimator { // The position for this child was never generated, let's continue. continue; } + if (loggable && isHeadsUp) { + mLogger.logHUNViewAppearingWithAddEvent(key); + } viewState.applyToView(changingView); mNewAddChildren.add(changingView); @@ -398,9 +404,18 @@ public class StackStateAnimator { translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f); } + Runnable postAnimation = changingView::removeFromTransientContainer; + if (loggable && isHeadsUp) { + mLogger.logHUNViewDisappearingWithRemoveEvent(key); + String finalKey = key; + postAnimation = () -> { + mLogger.disappearAnimationEnded(finalKey); + changingView.removeFromTransientContainer(); + }; + } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - 0, changingView::removeFromTransientContainer, null); + 0, postAnimation, null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { if (mHostLayout.isFullySwipedOut(changingView)) { @@ -430,8 +445,7 @@ public class StackStateAnimator { // this only captures HEADS_UP_APPEAR animations, but HUNs can appear with normal // ADD animations, which would not be logged here. if (loggable) { - mLogger.logHUNViewAppearing( - ((ExpandableNotificationRow) changingView).getEntry().getKey()); + mLogger.logHUNViewAppearing(key); } mTmpState.applyToView(changingView); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt index 4315265e79cc..77377af9ddfb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt @@ -24,6 +24,22 @@ class StackStateLogger @Inject constructor( }) } + fun logHUNViewDisappearingWithRemoveEvent(key: String) { + buffer.log(TAG, LogLevel.ERROR, { + str1 = key + }, { + "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE" + }) + } + + fun logHUNViewAppearingWithAddEvent(key: String) { + buffer.log(TAG, LogLevel.ERROR, { + str1 = key + }, { + "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" + }) + } + fun disappearAnimationEnded(key: String) { buffer.log(TAG, LogLevel.INFO, { str1 = key diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 04d3e9a01d86..6a78370c7dab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -180,6 +180,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final MetricsLogger mMetricsLogger; private final AuthController mAuthController; private final StatusBarStateController mStatusBarStateController; + private final LatencyTracker mLatencyTracker; private long mLastFpFailureUptimeMillis; private int mNumConsecutiveFpFailures; @@ -281,7 +282,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp AuthController authController, StatusBarStateController statusBarStateController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, - SessionTracker sessionTracker) { + SessionTracker sessionTracker, + LatencyTracker latencyTracker) { mContext = context; mPowerManager = powerManager; mShadeController = shadeController; @@ -289,6 +291,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mDozeParameters = dozeParameters; mUpdateMonitor.registerCallback(this); mMediaManager = notificationMediaManager; + mLatencyTracker = latencyTracker; wakefulnessLifecycle.addObserver(mWakefulnessObserver); screenLifecycle.addObserver(mScreenObserver); @@ -343,13 +346,13 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp public void onBiometricAcquired(BiometricSourceType biometricSourceType) { Trace.beginSection("BiometricUnlockController#onBiometricAcquired"); releaseBiometricWakeLock(); - if (!mUpdateMonitor.isDeviceInteractive()) { - if (LatencyTracker.isEnabled(mContext)) { + if (isWakeAndUnlock()) { + if (mLatencyTracker.isEnabled()) { int action = LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK; if (biometricSourceType == BiometricSourceType.FACE) { action = LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK; } - LatencyTracker.getInstance(mContext).onActionStart(action); + mLatencyTracker.onActionStart(action); } mWakeLock = mPowerManager.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, BIOMETRIC_WAKE_LOCK_NAME); @@ -652,6 +655,14 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)) .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId())); + if (mLatencyTracker.isEnabled()) { + int action = LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK; + if (biometricSourceType == BiometricSourceType.FACE) { + action = LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK; + } + mLatencyTracker.onActionCancel(action); + } + if (biometricSourceType == BiometricSourceType.FINGERPRINT && mUpdateMonitor.isUdfpsSupported()) { long currUptimeMillis = SystemClock.uptimeMillis(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 00b54e9e042a..2ec5f250eb48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; import static com.android.systemui.util.Utils.getStatusBarHeaderHeightKeyguard; @@ -27,7 +26,6 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.UserManager; import android.util.AttributeSet; import android.util.Pair; import android.util.TypedValue; @@ -47,7 +45,6 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; -import com.android.systemui.statusbar.window.StatusBarWindowView; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -72,7 +69,6 @@ public class KeyguardStatusBarView extends RelativeLayout { private StatusIconContainer mStatusIconContainer; private boolean mKeyguardUserSwitcherEnabled; - private final UserManager mUserManager; private boolean mIsPrivacyDotEnabled; private int mSystemIconsSwitcherHiddenExpandedMargin; @@ -99,10 +95,10 @@ public class KeyguardStatusBarView extends RelativeLayout { */ private int mTopClipping; private final Rect mClipRect = new Rect(0, 0, 0, 0); + private boolean mIsUserSwitcherEnabled; public KeyguardStatusBarView(Context context, AttributeSet attrs) { super(context, attrs); - mUserManager = UserManager.get(getContext()); } @Override @@ -163,6 +159,10 @@ public class KeyguardStatusBarView extends RelativeLayout { updateKeyguardStatusBarHeight(); } + public void setUserSwitcherEnabled(boolean enabled) { + mIsUserSwitcherEnabled = enabled; + } + private void updateKeyguardStatusBarHeight() { MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams(); lp.height = getStatusBarHeaderHeightKeyguard(mContext); @@ -200,11 +200,7 @@ public class KeyguardStatusBarView extends RelativeLayout { // If we have no keyguard switcher, the screen width is under 600dp. In this case, // we only show the multi-user switch if it's enabled through UserManager as well as // by the user. - // TODO(b/138661450) Move IPC calls to background - boolean isMultiUserEnabled = whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled( - mContext.getResources().getBoolean( - R.bool.qs_show_user_switcher_for_single_user))); - if (isMultiUserEnabled) { + if (mIsUserSwitcherEnabled) { mMultiUserAvatar.setVisibility(View.VISIBLE); } else { mMultiUserAvatar.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 81871634fbaf..ee97fd631818 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -23,8 +23,10 @@ import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedul import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.biometrics.BiometricSourceType; +import android.os.UserManager; import android.util.MathUtils; import android.view.View; @@ -92,6 +94,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private final BiometricUnlockController mBiometricUnlockController; private final SysuiStatusBarStateController mStatusBarStateController; private final StatusBarContentInsetsProvider mInsetsProvider; + private final UserManager mUserManager; private final ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @@ -105,6 +108,11 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mView.onOverlayChanged(); KeyguardStatusBarViewController.this.onThemeChanged(); } + + @Override + public void onConfigChanged(Configuration newConfig) { + updateUserSwitcher(); + } }; private final SystemStatusAnimationCallback mAnimationCallback = @@ -159,6 +167,13 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat } @Override + public void onKeyguardVisibilityChanged(boolean showing) { + if (showing) { + updateUserSwitcher(); + } + } + + @Override public void onBiometricRunningStateChanged( boolean running, BiometricSourceType biometricSourceType) { @@ -230,7 +245,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat KeyguardUpdateMonitor keyguardUpdateMonitor, BiometricUnlockController biometricUnlockController, SysuiStatusBarStateController statusBarStateController, - StatusBarContentInsetsProvider statusBarContentInsetsProvider + StatusBarContentInsetsProvider statusBarContentInsetsProvider, + UserManager userManager ) { super(view); mCarrierTextController = carrierTextController; @@ -248,6 +264,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mBiometricUnlockController = biometricUnlockController; mStatusBarStateController = statusBarStateController; mInsetsProvider = statusBarContentInsetsProvider; + mUserManager = userManager; mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled(); mKeyguardStateController.addCallback( @@ -293,7 +310,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat } mView.setOnApplyWindowInsetsListener( (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider)); - + updateUserSwitcher(); onThemeChanged(); } @@ -437,6 +454,14 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat } /** + * Updates visibility of the user switcher button based on {@link android.os.UserManager} state. + */ + private void updateUserSwitcher() { + mView.setUserSwitcherEnabled(mUserManager.isUserSwitcherEnabled(getResources().getBoolean( + R.bool.qs_show_user_switcher_for_single_user))); + } + + /** * Update {@link KeyguardStatusBarView}'s visibility based on whether keyguard is showing and * whether heads up is visible. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index c09c48540901..ebfed1a689cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -135,7 +135,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } }.setDuration(CONTENT_FADE_DURATION); - private static final int MAX_VISIBLE_ICONS_ON_LOCK = 5; + private static final int MAX_ICONS_ON_AOD = 3; + public static final int MAX_ICONS_ON_LOCKSCREEN = 3; public static final int MAX_STATIC_ICONS = 4; private static final int MAX_DOTS = 1; @@ -386,6 +387,19 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } /** + * @return Width of shelf for the given number of icons and overflow dot + */ + public int calculateWidthFor(int numMaxIcons) { + if (getChildCount() == 0) { + return 0; + } + return (int) (getActualPaddingStart() + + numMaxIcons * mIconSize + + mOverflowWidth + + getActualPaddingEnd()); + } + + /** * Calculate the horizontal translations for each notification based on how much the icons * are inserted into the notification container. * If this is not a whole number, the fraction means by how much the icon is appearing. @@ -394,7 +408,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { float translationX = getActualPaddingStart(); int firstOverflowIndex = -1; int childCount = getChildCount(); - int maxVisibleIcons = mOnLockScreen ? MAX_VISIBLE_ICONS_ON_LOCK : + int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD : mIsStaticLayout ? MAX_STATIC_ICONS : childCount; float layoutEnd = getLayoutEnd(); float overflowStart = getMaxOverflowStart(); @@ -414,7 +428,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons; - boolean noOverflowAfter = i == childCount - 1; + boolean isLastChild = i == childCount - 1; float drawingScale = mOnLockScreen && view instanceof StatusBarIconView ? ((StatusBarIconView) view).getIconScaleIncreased() : 1f; @@ -423,10 +437,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { : StatusBarIconView.STATE_ICON; boolean isOverflowing = - (translationX > (noOverflowAfter ? layoutEnd - mIconSize + (translationX > (isLastChild ? layoutEnd - mIconSize : overflowStart - mIconSize)); if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) { - firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i; + firstOverflowIndex = isLastChild && !forceOverflow ? i - 1 : i; mVisualOverflowStart = layoutEnd - mOverflowWidth; if (forceOverflow || mIsStaticLayout) { mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt new file mode 100644 index 000000000000..ff48755f750a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.content.Context +import android.view.ViewGroup +import com.android.systemui.R +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.LEFT +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.RIGHT +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate +import com.android.systemui.unfold.SysUIUnfoldScope +import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider +import javax.inject.Inject + +@SysUIUnfoldScope +class NotificationPanelUnfoldAnimationController +@Inject +constructor(private val context: Context, progressProvider: NaturalRotationUnfoldProgressProvider) { + + private val translateAnimator by lazy { + UnfoldConstantTranslateAnimator( + viewsIdToTranslate = + setOf( + ViewIdToTranslate(R.id.quick_settings_panel, LEFT), + ViewIdToTranslate(R.id.notification_stack_scroller, RIGHT), + ViewIdToTranslate(R.id.rightLayout, RIGHT), + ViewIdToTranslate(R.id.clock, LEFT), + ViewIdToTranslate(R.id.date, LEFT)), + progressProvider = progressProvider) + } + + fun setup(root: ViewGroup) { + val translationMax = + context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings).toFloat() + translateAnimator.init(root, translationMax) + } +} 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 9d3f19ad039a..3d3a1da1cf82 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -20,6 +20,7 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.view.View.GONE; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM; import static androidx.constraintlayout.widget.ConstraintSet.END; import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; import static androidx.constraintlayout.widget.ConstraintSet.START; @@ -414,6 +415,7 @@ public class NotificationPanelViewController extends PanelViewController private int mDisplayTopInset = 0; // in pixels private int mDisplayRightInset = 0; // in pixels private int mSplitShadeStatusBarHeight; + private int mSplitShadeNotificationsScrimMarginBottom; private final KeyguardClockPositionAlgorithm mClockPositionAlgorithm = @@ -669,6 +671,8 @@ public class NotificationPanelViewController extends PanelViewController private boolean mStatusViewCentered = true; private Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition; + private Optional<NotificationPanelUnfoldAnimationController> + mNotificationPanelUnfoldAnimationController; private final ListenerSet<Callbacks> mNotifEventSourceCallbacks = new ListenerSet<>(); @@ -929,6 +933,8 @@ public class NotificationPanelViewController extends PanelViewController mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count); mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition()); + mNotificationPanelUnfoldAnimationController = unfoldComponent.map( + SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController); mCommunalSourceMonitorCallback = (source) -> { mUiExecutor.execute(() -> setCommunalSource(source)); @@ -1064,6 +1070,8 @@ public class NotificationPanelViewController extends PanelViewController mTapAgainViewController.init(); mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView)); + mNotificationPanelUnfoldAnimationController.ifPresent(controller -> + controller.setup(mNotificationContainerParent)); } @Override @@ -1162,6 +1170,9 @@ public class NotificationPanelViewController extends PanelViewController public void updateResources() { mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext()); mSplitShadeStatusBarHeight = Utils.getSplitShadeStatusBarHeight(mView.getContext()); + mSplitShadeNotificationsScrimMarginBottom = + mResources.getDimensionPixelSize( + R.dimen.split_shade_notifications_scrim_margin_bottom); int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width); int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width); mShouldUseSplitNotificationShade = @@ -1171,6 +1182,8 @@ public class NotificationPanelViewController extends PanelViewController mQs.setInSplitShade(mShouldUseSplitNotificationShade); } + int notificationsBottomMargin = mResources.getDimensionPixelSize( + R.dimen.notification_panel_margin_bottom); int topMargin = mShouldUseSplitNotificationShade ? mSplitShadeStatusBarHeight : mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top); mSplitShadeHeaderController.setSplitShadeMode(mShouldUseSplitNotificationShade); @@ -1199,9 +1212,12 @@ public class NotificationPanelViewController extends PanelViewController constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth; constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth; constraintSet.setMargin(R.id.notification_stack_scroller, TOP, topMargin); + constraintSet.setMargin(R.id.notification_stack_scroller, BOTTOM, + notificationsBottomMargin); constraintSet.setMargin(R.id.qs_frame, TOP, topMargin); constraintSet.applyTo(mNotificationContainerParent); mAmbientState.setStackTopMargin(topMargin); + mNotificationsQSContainerController.updateMargins(); mNotificationsQSContainerController.setSplitShadeEnabled(mShouldUseSplitNotificationShade); updateKeyguardStatusViewAlignment(/* animate= */false); @@ -1319,6 +1335,7 @@ public class NotificationPanelViewController extends PanelViewController setKeyguardBottomAreaVisibility(mBarState, false); mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView)); + mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView)); } private void attachSplitShadeMediaPlayerContainer(FrameLayout container) { @@ -2558,7 +2575,8 @@ public class NotificationPanelViewController extends PanelViewController right = getView().getRight() + mDisplayRightInset; } else { top = Math.min(qsPanelBottomY, mSplitShadeStatusBarHeight); - bottom = top + mNotificationStackScrollLayoutController.getHeight(); + bottom = top + mNotificationStackScrollLayoutController.getHeight() + - mSplitShadeNotificationsScrimMarginBottom; left = mNotificationStackScrollLayoutController.getLeft(); right = mNotificationStackScrollLayoutController.getRight(); } @@ -4188,9 +4206,10 @@ public class NotificationPanelViewController extends PanelViewController return false; } - // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able - // to pull down QS or expand the shade. - if (mStatusBar.isBouncerShowingScrimmed()) { + // Do not allow panel expansion if bouncer is scrimmed or showing over a dream, + // otherwise user would be able to pull down QS or expand the shade. + if (mStatusBar.isBouncerShowingScrimmed() + || mStatusBar.isBouncerShowingOverDream()) { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt index 34bb6d3e1a27..1a885336be5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt @@ -63,7 +63,7 @@ class NotificationsQSContainerController @Inject constructor( } public override fun onViewAttached() { - notificationsBottomMargin = mView.defaultNotificationsMarginBottom + updateMargins() overviewProxyService.addCallback(taskbarVisibilityListener) mView.setInsetsChangedListener(windowInsetsListener) mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) } @@ -75,6 +75,10 @@ class NotificationsQSContainerController @Inject constructor( mView.removeQSFragmentAttachedListener() } + fun updateMargins() { + notificationsBottomMargin = mView.defaultNotificationsMarginBottom + } + override fun setCustomizerAnimating(animating: Boolean) { if (isQSCustomizerAnimating != animating) { isQSCustomizerAnimating = animating diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java index 9210a8b5db80..cecbcdb829c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java @@ -45,7 +45,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout private View mStackScroller; private View mKeyguardStatusBar; - private int mStackScrollerMargin; private ArrayList<View> mDrawingOrderedChildren = new ArrayList<>(); private ArrayList<View> mLayoutDrawingOrder = new ArrayList<>(); private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild); @@ -63,7 +62,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout super.onFinishInflate(); mQsFrame = findViewById(R.id.qs_frame); mStackScroller = findViewById(R.id.notification_stack_scroller); - mStackScrollerMargin = ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin; mKeyguardStatusBar = findViewById(R.id.keyguard_header); } @@ -97,7 +95,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout } public int getDefaultNotificationsMarginBottom() { - return mStackScrollerMargin; + return ((LayoutParams) mStackScroller.getLayoutParams()).bottomMargin; } public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index dc6efba97ff5..c466a8ce6d3f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -73,6 +73,10 @@ import java.io.PrintWriter; public abstract class PanelViewController { public static final boolean DEBUG = PanelView.DEBUG; public static final String TAG = PanelView.class.getSimpleName(); + public static final float FLING_MAX_LENGTH_SECONDS = 0.6f; + public static final float FLING_SPEED_UP_FACTOR = 0.6f; + public static final float FLING_CLOSING_MAX_LENGTH_SECONDS = 0.6f; + public static final float FLING_CLOSING_SPEED_UP_FACTOR = 0.6f; private static final int NO_FIXED_DURATION = -1; private static final long SHADE_OPEN_SPRING_OUT_DURATION = 350L; private static final long SHADE_OPEN_SPRING_BACK_DURATION = 400L; @@ -269,13 +273,13 @@ public abstract class PanelViewController { mNotificationShadeWindowController = notificationShadeWindowController; mFlingAnimationUtils = flingAnimationUtilsBuilder .reset() - .setMaxLengthSeconds(0.6f) - .setSpeedUpFactor(0.6f) + .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS) + .setSpeedUpFactor(FLING_SPEED_UP_FACTOR) .build(); mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder .reset() - .setMaxLengthSeconds(0.5f) - .setSpeedUpFactor(0.6f) + .setMaxLengthSeconds(FLING_CLOSING_MAX_LENGTH_SECONDS) + .setSpeedUpFactor(FLING_CLOSING_SPEED_UP_FACTOR) .build(); mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder .reset() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 1cb19ab727aa..d6bf5f21c834 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -49,29 +49,29 @@ class PhoneStatusBarViewController private constructor( } override fun onViewAttached() { - moveFromCenterAnimationController?.let { animationController -> - val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side) - val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area) - - val viewsToAnimate = arrayOf( - statusBarLeftSide, - systemIconArea - ) - - mView.viewTreeObserver.addOnPreDrawListener(object : - ViewTreeObserver.OnPreDrawListener { - override fun onPreDraw(): Boolean { - animationController.onViewsReady(viewsToAnimate) - mView.viewTreeObserver.removeOnPreDrawListener(this) - return true - } - }) + if (moveFromCenterAnimationController == null) return + + val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side) + val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area) + + val viewsToAnimate = arrayOf( + statusBarLeftSide, + systemIconArea + ) + + mView.viewTreeObserver.addOnPreDrawListener(object : + ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + moveFromCenterAnimationController.onViewsReady(viewsToAnimate) + mView.viewTreeObserver.removeOnPreDrawListener(this) + return true + } + }) - mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ -> - val widthChanged = right - left != oldRight - oldLeft - if (widthChanged) { - moveFromCenterAnimationController.onStatusBarWidthChanged() - } + mView.addOnLayoutChangeListener { _, left, _, right, _, oldLeft, _, oldRight, _ -> + val widthChanged = right - left != oldRight - oldLeft + if (widthChanged) { + moveFromCenterAnimationController.onStatusBarWidthChanged() } } @@ -162,9 +162,7 @@ class PhoneStatusBarViewController private constructor( PhoneStatusBarViewController( view, progressProvider.getOrNull(), - unfoldComponent.map { - it.getStatusBarMoveFromCenterAnimationController() - }.getOrNull(), + unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(), touchEventHandler, configurationController ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt index 091831f36022..ea61a8b94e7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt @@ -113,14 +113,16 @@ class ScreenOffAnimationController @Inject constructor( * the animation ends */ fun shouldDelayKeyguardShow(): Boolean = - animations.any { it.shouldPlayAnimation() } + animations.any { it.shouldDelayKeyguardShow() } /** * Return true while we want to ignore requests to show keyguard, we need to handle pending * keyguard lock requests manually + * + * @see [com.android.systemui.keyguard.KeyguardViewMediator.maybeHandlePendingLock] */ fun isKeyguardShowDelayed(): Boolean = - animations.any { it.isAnimationPlaying() } + animations.any { it.isKeyguardShowDelayed() } /** * Return true to ignore requests to hide keyguard @@ -211,6 +213,8 @@ interface ScreenOffAnimation { fun shouldAnimateInKeyguard(): Boolean = false fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run() + fun shouldDelayKeyguardShow(): Boolean = false + fun isKeyguardShowDelayed(): Boolean = false fun isKeyguardHideDelayed(): Boolean = false fun shouldHideScrimOnWakeUp(): Boolean = false fun overrideNotificationsDozeAmount(): Boolean = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index d2e1650056ac..ef5f21658d83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -62,7 +62,7 @@ public enum ScrimState { public void prepare(ScrimState previousState) { mBlankScreen = false; if (previousState == ScrimState.AOD) { - mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP; + mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP_SCRIM; if (mDisplayRequiresBlanking) { // DisplayPowerManager will blank the screen, we'll just // set our scrim to black in this frame to avoid flickering and @@ -70,7 +70,7 @@ public enum ScrimState { mBlankScreen = true; } } else if (previousState == ScrimState.KEYGUARD) { - mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP; + mAnimationDuration = StackStateAnimator.ANIMATION_DURATION_WAKEUP_SCRIM; } else { mAnimationDuration = ScrimController.ANIMATION_DURATION; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index c09c3ca9dede..2f3300a53ad6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -150,6 +150,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.emergency.EmergencyGesture; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -338,6 +339,7 @@ public class StatusBar extends CoreStartable implements } private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final DreamOverlayStateController mDreamOverlayStateController; private StatusBarCommandQueueCallbacks mCommandQueueCallbacks; void onStatusBarWindowStateChanged(@WindowVisibleState int state) { @@ -781,7 +783,8 @@ public class StatusBar extends CoreStartable implements ActivityLaunchAnimator activityLaunchAnimator, NotifPipelineFlags notifPipelineFlags, InteractionJankMonitor jankMonitor, - DeviceStateManager deviceStateManager) { + DeviceStateManager deviceStateManager, + DreamOverlayStateController dreamOverlayStateController) { super(context); mNotificationsController = notificationsController; mFragmentService = fragmentService; @@ -869,6 +872,7 @@ public class StatusBar extends CoreStartable implements mMessageRouter = messageRouter; mWallpaperManager = wallpaperManager; mJankMonitor = jankMonitor; + mDreamOverlayStateController = dreamOverlayStateController; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -2983,6 +2987,7 @@ public class StatusBar extends CoreStartable implements } public void showKeyguardImpl() { + Trace.beginSection("StatusBar#showKeyguard"); mIsKeyguard = true; // In case we're locking while a smartspace transition is in progress, reset it. mKeyguardUnlockAnimationController.resetSmartspaceTransition(); @@ -2997,6 +3002,7 @@ public class StatusBar extends CoreStartable implements mStatusBarStateController.setState(StatusBarState.KEYGUARD); } updatePanelExpansionForKeyguard(); + Trace.endSection(); } private void updatePanelExpansionForKeyguard() { @@ -4144,6 +4150,10 @@ public class StatusBar extends CoreStartable implements return isBouncerShowing() && mStatusBarKeyguardViewManager.bouncerNeedsScrimming(); } + public boolean isBouncerShowingOverDream() { + return isBouncerShowing() && mDreamOverlayStateController.isOverlayActive(); + } + /** * When {@link KeyguardBouncer} starts to be dismissed, playing its animation. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 316e68227e0c..c2b24e523943 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -29,6 +29,7 @@ import android.content.res.ColorStateList; import android.hardware.biometrics.BiometricSourceType; import android.os.Bundle; import android.os.SystemClock; +import android.os.Trace; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -48,9 +49,9 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.DejankUtils; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -111,6 +112,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final NotificationShadeWindowController mNotificationShadeWindowController; private final KeyguardBouncer.Factory mKeyguardBouncerFactory; private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory; + private final DreamOverlayStateController mDreamOverlayStateController; private KeyguardMessageAreaController mKeyguardMessageAreaController; private final Lazy<ShadeController> mShadeController; private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() { @@ -211,6 +213,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final SysuiStatusBarStateController mStatusBarStateController; private final DockManager mDockManager; private final KeyguardUpdateMonitor mKeyguardUpdateManager; + private final LatencyTracker mLatencyTracker; private KeyguardBypassController mBypassController; @Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor; @@ -235,6 +238,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb SysuiStatusBarStateController sysuiStatusBarStateController, ConfigurationController configurationController, KeyguardUpdateMonitor keyguardUpdateMonitor, + DreamOverlayStateController dreamOverlayStateController, NavigationModeController navigationModeController, DockManager dockManager, NotificationShadeWindowController notificationShadeWindowController, @@ -242,13 +246,15 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb NotificationMediaManager notificationMediaManager, KeyguardBouncer.Factory keyguardBouncerFactory, KeyguardMessageAreaController.Factory keyguardMessageAreaFactory, - Lazy<ShadeController> shadeController) { + Lazy<ShadeController> shadeController, + LatencyTracker latencyTracker) { mContext = context; mViewMediatorCallback = callback; mLockPatternUtils = lockPatternUtils; mConfigurationController = configurationController; mNavigationModeController = navigationModeController; mNotificationShadeWindowController = notificationShadeWindowController; + mDreamOverlayStateController = dreamOverlayStateController; mKeyguardStateController = keyguardStateController; mMediaManager = notificationMediaManager; mKeyguardUpdateManager = keyguardUpdateMonitor; @@ -257,6 +263,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardBouncerFactory = keyguardBouncerFactory; mKeyguardMessageAreaFactory = keyguardMessageAreaFactory; mShadeController = shadeController; + mLatencyTracker = latencyTracker; } @Override @@ -370,6 +377,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ @Override public void show(Bundle options) { + Trace.beginSection("StatusBarKeyguardViewManager#show"); mShowing = true; mNotificationShadeWindowController.setKeyguardShowing(true); mKeyguardStateController.notifyKeyguardState(mShowing, @@ -377,6 +385,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb reset(true /* hideBouncerWhenShowing */); SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); + Trace.endSection(); } /** @@ -711,6 +720,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void hide(long startTime, long fadeoutDuration) { + Trace.beginSection("StatusBarKeyguardViewManager#hide"); mShowing = false; mKeyguardStateController.notifyKeyguardState(mShowing, mKeyguardStateController.isOccluded()); @@ -810,6 +820,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN); + Trace.endSection(); } private boolean needsBypassFading() { @@ -850,15 +861,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } private void wakeAndUnlockDejank() { - if (mBiometricUnlockController.getMode() == MODE_WAKE_AND_UNLOCK - && LatencyTracker.isEnabled(mContext)) { + if (mBiometricUnlockController.isWakeAndUnlock() && mLatencyTracker.isEnabled()) { BiometricSourceType type = mBiometricUnlockController.getBiometricType(); - DejankUtils.postAfterTraversal(() -> { - LatencyTracker.getInstance(mContext).onActionEnd( - type == BiometricSourceType.FACE - ? LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK - : LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK); - }); + mLatencyTracker.onActionEnd(type == BiometricSourceType.FACE + ? LatencyTracker.ACTION_FACE_WAKE_AND_UNLOCK + : LatencyTracker.ACTION_FINGERPRINT_WAKE_AND_UNLOCK); } } @@ -1174,7 +1181,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public boolean bouncerNeedsScrimming() { - return mOccluded || mBouncer.willDismissWithAction() + // When a dream overlay is active, scrimming will cause any expansion to immediately expand. + return (mOccluded && !mDreamOverlayStateController.isOverlayActive()) + || mBouncer.willDismissWithAction() || mStatusBar.isFullScreenUserSwitcherState() || (mBouncer.isShowing() && mBouncer.isScrimmed()) || mBouncer.isFullscreenBouncer(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt index 6d033477e3c9..79c0984d9bf7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt @@ -18,11 +18,13 @@ package com.android.systemui.statusbar.phone import android.view.View import android.view.WindowManager import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator +import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.AlphaProvider import com.android.systemui.statusbar.phone.PhoneStatusBarViewController.StatusBarViewsCenterProvider import com.android.systemui.unfold.SysUIUnfoldScope import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import javax.inject.Inject +import kotlin.math.max @SysUIUnfoldScope class StatusBarMoveFromCenterAnimationController @Inject constructor( @@ -31,8 +33,11 @@ class StatusBarMoveFromCenterAnimationController @Inject constructor( ) { private val transitionListener = TransitionListener() - private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(windowManager, - viewCenterProvider = StatusBarViewsCenterProvider()) + private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator( + windowManager, + viewCenterProvider = StatusBarViewsCenterProvider(), + alphaProvider = StatusBarIconsAlphaProvider() + ) fun onViewsReady(viewsToAnimate: Array<View>) { moveFromCenterAnimator.updateDisplayProperties() @@ -65,4 +70,15 @@ class StatusBarMoveFromCenterAnimationController @Inject constructor( moveFromCenterAnimator.onTransitionProgress(1f) } } + + private class StatusBarIconsAlphaProvider : AlphaProvider { + override fun getAlpha(progress: Float): Float { + return max( + 0f, + (progress - ICONS_START_APPEARING_PROGRESS) / (1 - ICONS_START_APPEARING_PROGRESS) + ) + } + } } + +private const val ICONS_START_APPEARING_PROGRESS = 0.75F diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index e3b4caabb134..d6fc0a426590 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -23,6 +23,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; +import android.graphics.Insets; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -87,11 +89,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh this(context, theme, dismissOnDeviceLock, null); } - /** - * @param udfpsDialogManager If set, UDFPS will hide if this dialog is showing. - */ public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock, - SystemUIDialogManager dialogManager) { + @Nullable SystemUIDialogManager dialogManager) { super(context, theme); mContext = context; @@ -148,7 +147,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh * the device configuration changes, and the result will be used to resize this dialog window. */ protected int getWidth() { - return getDefaultDialogWidth(mContext); + return getDefaultDialogWidth(this); } /** @@ -279,36 +278,53 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh // We need to create the dialog first, otherwise the size will be overridden when it is // created. dialog.create(); - dialog.getWindow().setLayout(getDefaultDialogWidth(dialog.getContext()), - getDefaultDialogHeight()); + dialog.getWindow().setLayout(getDefaultDialogWidth(dialog), getDefaultDialogHeight()); } - private static int getDefaultDialogWidth(Context context) { - boolean isOnTablet = context.getResources().getConfiguration().smallestScreenWidthDp >= 600; - if (!isOnTablet) { - return ViewGroup.LayoutParams.MATCH_PARENT; - } - + private static int getDefaultDialogWidth(Dialog dialog) { + Context context = dialog.getContext(); int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0); if (flagValue == -1) { // The width of bottom sheets (624dp). - return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624, - context.getResources().getDisplayMetrics())); + return calculateDialogWidthWithInsets(dialog, 624); } else if (flagValue == -2) { // The suggested small width for all dialogs (348dp) - return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348, - context.getResources().getDisplayMetrics())); + return calculateDialogWidthWithInsets(dialog, 348); } else if (flagValue > 0) { // Any given width. - return Math.round( - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue, - context.getResources().getDisplayMetrics())); + return calculateDialogWidthWithInsets(dialog, flagValue); } else { - // By default we use the same width as the notification shade in portrait mode (504dp). - return context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width); + // By default we use the same width as the notification shade in portrait mode. + int width = context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width); + if (width > 0) { + // If we are neither WRAP_CONTENT or MATCH_PARENT, add the background insets so that + // the dialog is the desired width. + width += getHorizontalInsets(dialog); + } + return width; } } + /** + * Return the pixel width {@param dialog} should be so that it is {@param widthInDp} wide, + * taking its background insets into consideration. + */ + private static int calculateDialogWidthWithInsets(Dialog dialog, int widthInDp) { + float widthInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, widthInDp, + dialog.getContext().getResources().getDisplayMetrics()); + return Math.round(widthInPixels + getHorizontalInsets(dialog)); + } + + private static int getHorizontalInsets(Dialog dialog) { + if (dialog.getWindow().getDecorView() == null) { + return 0; + } + + Drawable background = dialog.getWindow().getDecorView().getBackground(); + Insets insets = background != null ? background.getOpticalInsets() : Insets.NONE; + return insets.left + insets.right; + } + private static int getDefaultDialogHeight() { return ViewGroup.LayoutParams.WRAP_CONTENT; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index cc65ca025139..0abe8e489638 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -136,6 +136,12 @@ class UnlockedScreenOffAnimationController @Inject constructor( globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) } + override fun shouldDelayKeyguardShow(): Boolean = + shouldPlayAnimation() + + override fun isKeyguardShowDelayed(): Boolean = + isAnimationPlaying() + /** * Animates in the provided keyguard view, ending in the same position that it will be in on * AOD. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index f5364b9363b9..d3ff4a78c893 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -40,6 +40,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.DemoModeController; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; @@ -231,7 +232,8 @@ public interface StatusBarPhoneModule { ActivityLaunchAnimator activityLaunchAnimator, NotifPipelineFlags notifPipelineFlags, InteractionJankMonitor jankMonitor, - DeviceStateManager deviceStateManager) { + DeviceStateManager deviceStateManager, + DreamOverlayStateController dreamOverlayStateController) { return new StatusBar( context, notificationsController, @@ -327,7 +329,8 @@ public interface StatusBarPhoneModule { activityLaunchAnimator, notifPipelineFlags, jankMonitor, - deviceStateManager + deviceStateManager, + dreamOverlayStateController ); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 48949f92413d..3205e097c03b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -118,6 +118,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private boolean mColorized; private int mTint; private boolean mResetting; + private boolean mWasSpinning; // TODO(b/193539698): move these to a Controller private RemoteInputController mController; @@ -439,6 +440,10 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEditText.requestFocus(); } } + if (mWasSpinning) { + mController.addSpinning(mEntry.getKey(), mToken); + mWasSpinning = false; + } } @Override @@ -447,6 +452,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene mEditText.removeTextChangedListener(mTextWatcher); mEditText.setOnEditorActionListener(null); mEditText.mRemoteInputView = null; + mWasSpinning = mController.isSpinning(mEntry.getKey(), mToken); if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) { return; } @@ -533,6 +539,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene if (isActive() && mWrapper != null) { mWrapper.setRemoteInputVisible(true); } + + mWasSpinning = false; } private void reset() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 9f20bc55ebc9..49e712d386e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -455,6 +455,13 @@ public class UserSwitcherController implements Dumpable { } } + /** + * Returns whether the current user is a system user. + */ + public boolean isSystemUser() { + return mUserTracker.getUserId() == UserHandle.USER_SYSTEM; + } + public void removeUserId(int userId) { if (userId == UserHandle.USER_SYSTEM) { Log.w(TAG, "User " + userId + " could not removed."); diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java index e59d2f233804..d0fb91c9342a 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java @@ -18,6 +18,7 @@ package com.android.systemui.tv; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.GlobalRootComponent; +import com.android.systemui.statusbar.tv.VpnStatusObserver; import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler; import dagger.Binds; @@ -34,4 +35,9 @@ interface TvSystemUIBinder { @IntoMap @ClassKey(TvNotificationHandler.class) CoreStartable bindTvNotificationHandler(TvNotificationHandler systemui); + + @Binds + @IntoMap + @ClassKey(VpnStatusObserver.class) + CoreStartable bindVpnStatusObserver(VpnStatusObserver systemui); } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index c481fc94c526..2e627a872c24 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -20,7 +20,6 @@ import android.os.Handler import android.os.PowerManager import android.provider.Settings import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.phone.ScreenOffAnimation @@ -28,7 +27,6 @@ import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus import com.android.systemui.util.settings.GlobalSettings -import dagger.Lazy import javax.inject.Inject /** @@ -40,7 +38,6 @@ class FoldAodAnimationController @Inject constructor( @Main private val handler: Handler, - private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>, private val wakefulnessLifecycle: WakefulnessLifecycle, private val globalSettings: GlobalSettings ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer { @@ -57,7 +54,6 @@ constructor( statusBar.notificationPanelViewController.startFoldToAodAnimation { // End action isAnimationPlaying = false - keyguardViewMediatorLazy.get().maybeHandlePendingLock() } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt index 07f9c5487c41..7350b37e4b66 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt @@ -18,6 +18,7 @@ package com.android.systemui.unfold import com.android.keyguard.KeyguardUnfoldTransition import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.phone.NotificationPanelUnfoldAnimationController import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider @@ -85,6 +86,8 @@ interface SysUIUnfoldComponent { fun getStatusBarMoveFromCenterAnimationController(): StatusBarMoveFromCenterAnimationController + fun getNotificationPanelUnfoldAnimationController(): NotificationPanelUnfoldAnimationController + fun getFoldAodAnimationController(): FoldAodAnimationController fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java new file mode 100644 index 000000000000..768743217cc7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.service; + +/** + * The {@link Observer} interface specifies an entity which listeners + * can be informed of changes to the source, which will require updating. Note that this deals + * with changes to the source itself, not content which will be updated through the interface. + */ +public interface Observer { + /** + * Callback for receiving updates from the {@link Observer}. + */ + interface Callback { + /** + * Invoked when the source has changed. + */ + void onSourceChanged(); + } + + /** + * Adds a callback to receive future updates from the {@link Observer}. + */ + void addCallback(Callback callback); + + /** + * Removes a callback from receiving further updates. + * @param callback + */ + void removeCallback(Callback callback); +} diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java b/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java new file mode 100644 index 000000000000..2ee7b20c1f93 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/service/PackageObserver.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.service; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.PatternMatcher; +import android.util.Log; + +import com.android.systemui.communal.CommunalSource; + +import com.google.android.collect.Lists; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; + +import javax.inject.Inject; + +/** + * {@link PackageObserver} allows for monitoring the system for changes relating to a particular + * package. This can be used by {@link CommunalSource} clients to detect when a related package + * has changed and reloading is necessary. + */ +public class PackageObserver implements Observer { + private static final String TAG = "PackageObserver"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList(); + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Log.d(TAG, "package added receiver - onReceive"); + } + + final Iterator<WeakReference<Callback>> iter = mCallbacks.iterator(); + while (iter.hasNext()) { + final Callback callback = iter.next().get(); + if (callback != null) { + callback.onSourceChanged(); + } else { + iter.remove(); + } + } + } + }; + + private final String mPackageName; + private final Context mContext; + + @Inject + public PackageObserver(Context context, ComponentName component) { + mContext = context; + mPackageName = component.getPackageName(); + } + + @Override + public void addCallback(Callback callback) { + if (DEBUG) { + Log.d(TAG, "addCallback:" + callback); + } + mCallbacks.add(new WeakReference<>(callback)); + + // Only register for listening to package additions on first callback. + if (mCallbacks.size() > 1) { + return; + } + + final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addDataScheme("package"); + filter.addDataSchemeSpecificPart(mPackageName, PatternMatcher.PATTERN_LITERAL); + // Note that we directly register the receiver here as data schemes are not supported by + // BroadcastDispatcher. + mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED); + } + + @Override + public void removeCallback(Callback callback) { + if (DEBUG) { + Log.d(TAG, "removeCallback:" + callback); + } + final boolean removed = mCallbacks.removeIf(el -> el.get() == callback); + + if (removed && mCallbacks.isEmpty()) { + mContext.unregisterReceiver(mReceiver); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java new file mode 100644 index 000000000000..292c062369c1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.service; + +import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS; +import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS; +import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS; +import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER; +import static com.android.systemui.util.service.dagger.ObservableServiceModule.SERVICE_CONNECTION; + +import android.util.Log; + +import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.time.SystemClock; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * The {@link PersistentConnectionManager} is responsible for maintaining a connection to a + * {@link ObservableServiceConnection}. + * @param <T> The transformed connection type handled by the service. + */ +public class PersistentConnectionManager<T> { + private static final String TAG = "PersistentConnManager"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final SystemClock mSystemClock; + private final DelayableExecutor mMainExecutor; + private final int mBaseReconnectDelayMs; + private final int mMaxReconnectAttempts; + private final int mMinConnectionDuration; + private final Observer mObserver; + + private int mReconnectAttempts = 0; + private Runnable mCurrentReconnectCancelable; + + private final ObservableServiceConnection<T> mConnection; + + private final Runnable mConnectRunnable = new Runnable() { + @Override + public void run() { + mCurrentReconnectCancelable = null; + mConnection.bind(); + } + }; + + private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt(); + + private final ObservableServiceConnection.Callback mConnectionCallback = + new ObservableServiceConnection.Callback() { + private long mStartTime; + + @Override + public void onConnected(ObservableServiceConnection connection, Object proxy) { + mStartTime = mSystemClock.currentTimeMillis(); + } + + @Override + public void onDisconnected(ObservableServiceConnection connection, int reason) { + if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) { + initiateConnectionAttempt(); + } else { + scheduleConnectionAttempt(); + } + } + }; + + @Inject + public PersistentConnectionManager( + SystemClock clock, + DelayableExecutor mainExecutor, + @Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection, + @Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts, + @Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs, + @Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs, + @Named(OBSERVER) Observer observer) { + mSystemClock = clock; + mMainExecutor = mainExecutor; + mConnection = serviceConnection; + mObserver = observer; + + mMaxReconnectAttempts = maxReconnectAttempts; + mBaseReconnectDelayMs = baseReconnectDelayMs; + mMinConnectionDuration = minConnectionDurationMs; + } + + /** + * Begins the {@link PersistentConnectionManager} by connecting to the associated service. + */ + public void start() { + mConnection.addCallback(mConnectionCallback); + mObserver.addCallback(mObserverCallback); + initiateConnectionAttempt(); + } + + /** + * Brings down the {@link PersistentConnectionManager}, disconnecting from the service. + */ + public void stop() { + mConnection.removeCallback(mConnectionCallback); + mObserver.removeCallback(mObserverCallback); + mConnection.unbind(); + } + + private void initiateConnectionAttempt() { + // Reset attempts + mReconnectAttempts = 0; + + // The first attempt is always a direct invocation rather than delayed. + mConnection.bind(); + } + + private void scheduleConnectionAttempt() { + // always clear cancelable if present. + if (mCurrentReconnectCancelable != null) { + mCurrentReconnectCancelable.run(); + mCurrentReconnectCancelable = null; + } + + if (mReconnectAttempts >= mMaxReconnectAttempts) { + if (DEBUG) { + Log.d(TAG, "exceeded max connection attempts."); + } + return; + } + + final long reconnectDelayMs = + (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts); + + if (DEBUG) { + Log.d(TAG, + "scheduling connection attempt in " + reconnectDelayMs + "milliseconds"); + } + + mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable, + reconnectDelayMs); + + mReconnectAttempts++; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java new file mode 100644 index 000000000000..c62c95755ce8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/ObservableServiceModule.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.systemui.util.service.dagger; + +import android.content.res.Resources; + +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; + +/** + * Module containing components and parameters for + * {@link com.android.systemui.util.service.ObservableServiceConnection} + * and {@link com.android.systemui.util.service.PersistentConnectionManager}. + */ +@Module(subcomponents = { + PackageObserverComponent.class, +}) +public class ObservableServiceModule { + public static final String MAX_RECONNECT_ATTEMPTS = "max_reconnect_attempts"; + public static final String BASE_RECONNECT_DELAY_MS = "base_reconnect_attempts"; + public static final String MIN_CONNECTION_DURATION_MS = "min_connection_duration_ms"; + public static final String SERVICE_CONNECTION = "service_connection"; + public static final String OBSERVER = "observer"; + + @Provides + @Named(MAX_RECONNECT_ATTEMPTS) + static int providesMaxReconnectAttempts(@Main Resources resources) { + return resources.getInteger( + R.integer.config_communalSourceMaxReconnectAttempts); + } + + @Provides + @Named(BASE_RECONNECT_DELAY_MS) + static int provideBaseReconnectDelayMs(@Main Resources resources) { + return resources.getInteger( + R.integer.config_communalSourceReconnectBaseDelay); + } + + @Provides + @Named(MIN_CONNECTION_DURATION_MS) + static int providesMinConnectionDuration(@Main Resources resources) { + return resources.getInteger( + R.integer.config_connectionMinDuration); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java b/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java new file mode 100644 index 000000000000..8ee39b34cdf5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/service/dagger/PackageObserverComponent.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.util.service.dagger; + +import android.content.ComponentName; + +import com.android.systemui.util.service.PackageObserver; + +import dagger.BindsInstance; +import dagger.Subcomponent; + +/** + * Generates a scoped {@link PackageObserver}. + */ +@Subcomponent +public interface PackageObserverComponent { + /** + * Generates a {@link PackageObserverComponent} instance. + */ + @Subcomponent.Factory + interface Factory { + PackageObserverComponent create(@BindsInstance ComponentName component); + } + + /** + * Creates a {@link PackageObserver}. + */ + PackageObserver getPackageObserver(); +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 11725ef4867e..57c7f11b752d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -33,7 +33,6 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.IAudioService; import android.media.IVolumeController; -import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.media.RoutingSessionInfo; import android.media.VolumePolicy; @@ -47,7 +46,6 @@ import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; import android.os.VibrationEffect; -import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Condition; import android.service.notification.ZenModeConfig; @@ -68,6 +66,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.qs.tiles.DndTile; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.concurrency.ThreadFactory; @@ -78,7 +77,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import javax.inject.Inject; @@ -135,7 +133,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa protected C mCallbacks = new C(); private final State mState = new State(); protected final MediaSessionsCallbacks mMediaSessionsCallbacksW; - private final Optional<Vibrator> mVibrator; + private final VibratorHelper mVibrator; private final boolean mHasVibrator; private boolean mShowA11yStream; private boolean mShowVolumeDialog; @@ -173,7 +171,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa ThreadFactory theadFactory, AudioManager audioManager, NotificationManager notificationManager, - Optional<Vibrator> optionalVibrator, + VibratorHelper vibrator, IAudioService iAudioService, AccessibilityManager accessibilityManager, PackageManager packageManager, @@ -199,8 +197,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mBroadcastDispatcher = broadcastDispatcher; mObserver.init(); mReceiver.init(); - mVibrator = optionalVibrator; - mHasVibrator = mVibrator.isPresent() && mVibrator.get().hasVibrator(); + mVibrator = vibrator; + mHasVibrator = mVibrator.hasVibrator(); mAudioService = iAudioService; boolean accessibilityVolumeStreamActive = accessibilityManager @@ -393,8 +391,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } public void vibrate(VibrationEffect effect) { - mVibrator.ifPresent( - vibrator -> vibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES)); + mVibrator.vibrate(effect, SONIFICIATION_VIBRATION_ATTRIBUTES); } public boolean hasVibrator() { @@ -402,7 +399,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } private void onNotifyVisibleW(boolean visible) { - if (mDestroyed) return; + if (mDestroyed) return; mAudio.notifyVolumeControllerVisible(mVolumeController, visible); if (!visible) { if (updateActiveStreamW(-1)) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt index 164f83dda9b7..6c1f008e9337 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt @@ -20,23 +20,21 @@ import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewGroup import androidx.test.filters.SmallTest -import com.android.keyguard.KeyguardUnfoldTransition.Companion.LEFT -import com.android.keyguard.KeyguardUnfoldTransition.Companion.RIGHT import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider import com.android.systemui.util.mockito.capture -import org.junit.Assert.assertEquals +import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations /** * Translates items away/towards the hinge when the device is opened/closed. This is controlled by @@ -46,14 +44,11 @@ import org.mockito.Mockito.verify @RunWith(AndroidTestingRunner::class) class KeyguardUnfoldTransitionTest : SysuiTestCase() { - @Mock - private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider + @Mock private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider - @Captor - private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener> + @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener> - @Mock - private lateinit var parent: ViewGroup + @Mock private lateinit var parent: ViewGroup private lateinit var keyguardUnfoldTransition: KeyguardUnfoldTransition private lateinit var progressListener: TransitionProgressListener @@ -63,87 +58,35 @@ class KeyguardUnfoldTransitionTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) - xTranslationMax = context.resources.getDimensionPixelSize( - R.dimen.keyguard_unfold_translation_x).toFloat() + xTranslationMax = + context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat() - keyguardUnfoldTransition = KeyguardUnfoldTransition( - getContext(), - progressProvider - ) - - verify(progressProvider).addCallback(capture(progressListenerCaptor)) - progressListener = progressListenerCaptor.value + keyguardUnfoldTransition = KeyguardUnfoldTransition(context, progressProvider) keyguardUnfoldTransition.setup(parent) keyguardUnfoldTransition.statusViewCentered = false - } - - @Test - fun onTransition_noMatchingIds() { - // GIVEN no views matching any ids - // WHEN the transition starts - progressListener.onTransitionStarted() - progressListener.onTransitionProgress(.1f) - - // THEN nothing... no exceptions - } - @Test - fun onTransition_oneMovesLeft() { - // GIVEN one view with a matching id - val view = View(getContext()) - `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(view) - - moveAndValidate(listOf(view to LEFT)) - } - - @Test - fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() { - // GIVEN two views with a matching id - val leftView = View(getContext()) - val rightView = View(getContext()) - `when`(parent.findViewById<View>(R.id.keyguard_status_area)).thenReturn(leftView) - `when`(parent.findViewById<View>(R.id.notification_stack_scroller)).thenReturn(rightView) - - moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT)) - moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT)) + verify(progressProvider).addCallback(capture(progressListenerCaptor)) + progressListener = progressListenerCaptor.value } @Test fun onTransition_centeredViewDoesNotMove() { keyguardUnfoldTransition.statusViewCentered = true - val view = View(getContext()) + val view = View(context) `when`(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view) - moveAndValidate(listOf(view to 0)) - } - - private fun moveAndValidate(list: List<Pair<View, Int>>) { - // Compare values as ints because -0f != 0f - - // WHEN the transition starts progressListener.onTransitionStarted() + assertThat(view.translationX).isZero() + progressListener.onTransitionProgress(0f) + assertThat(view.translationX).isZero() + + progressListener.onTransitionProgress(0.5f) + assertThat(view.translationX).isZero() - list.forEach { (view, direction) -> - assertEquals((-xTranslationMax * direction).toInt(), view.getTranslationX().toInt()) - } - - // WHEN the transition progresses, translation is updated - progressListener.onTransitionProgress(.5f) - list.forEach { (view, direction) -> - assertEquals( - (-xTranslationMax / 2f * direction).toInt(), - view.getTranslationX().toInt() - ) - } - - // WHEN the transition ends, translation is completed - progressListener.onTransitionProgress(1f) progressListener.onTransitionFinished() - list.forEach { (view, _) -> - assertEquals(0, view.getTranslationX().toInt()) - } + assertThat(view.translationX).isZero() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index 5d39eef999d7..c37e966f3540 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -27,10 +27,12 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -46,6 +48,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.ComponentInfoInternal; +import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; @@ -70,6 +73,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.concurrency.Execution; import com.android.systemui.util.concurrency.FakeExecution; @@ -80,6 +84,7 @@ import org.junit.runner.RunWith; import org.mockito.AdditionalMatchers; import org.mockito.ArgumentCaptor; import org.mockito.Captor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -99,6 +104,8 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private IBiometricSysuiReceiver mReceiver; @Mock + private IBiometricContextListener mContextListener; + @Mock private AuthDialog mDialog1; @Mock private AuthDialog mDialog2; @@ -120,10 +127,14 @@ public class AuthControllerTest extends SysuiTestCase { private DisplayManager mDisplayManager; @Mock private WakefulnessLifecycle mWakefulnessLifecycle; + @Mock + private StatusBarStateController mStatusBarStateController; @Captor ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mAuthenticatorsRegisteredCaptor; @Captor ArgumentCaptor<FingerprintStateListener> mFingerprintStateCaptor; + @Captor + ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; private TestableContext mContextSpy; private Execution mExecution; @@ -175,12 +186,15 @@ public class AuthControllerTest extends SysuiTestCase { mAuthController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, mFaceManager, - () -> mUdfpsController, () -> mSidefpsController); + () -> mUdfpsController, () -> mSidefpsController, mStatusBarStateController); mAuthController.start(); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( mAuthenticatorsRegisteredCaptor.capture()); + when(mStatusBarStateController.isDozing()).thenReturn(false); + verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); + mAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(props); // Ensures that the operations posted on the handler get executed. @@ -198,7 +212,8 @@ public class AuthControllerTest extends SysuiTestCase { // This test requires an uninitialized AuthController. AuthController authController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, - mFaceManager, () -> mUdfpsController, () -> mSidefpsController); + mFaceManager, () -> mUdfpsController, () -> mSidefpsController, + mStatusBarStateController); authController.start(); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( @@ -221,7 +236,8 @@ public class AuthControllerTest extends SysuiTestCase { // This test requires an uninitialized AuthController. AuthController authController = new TestableAuthController(mContextSpy, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager, - mFaceManager, () -> mUdfpsController, () -> mSidefpsController); + mFaceManager, () -> mUdfpsController, () -> mSidefpsController, + mStatusBarStateController); authController.start(); verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( @@ -656,6 +672,19 @@ public class AuthControllerTest extends SysuiTestCase { verify(callback).onBiometricPromptDismissed(); } + @Test + public void testForwardsDozeEvent() throws RemoteException { + mAuthController.setBiometicContextListener(mContextListener); + + mStatusBarStateListenerCaptor.getValue().onDozingChanged(false); + mStatusBarStateListenerCaptor.getValue().onDozingChanged(true); + + InOrder order = inOrder(mContextListener); + // invoked twice since the initial state is false + order.verify(mContextListener, times(2)).onDozeChanged(eq(false)); + order.verify(mContextListener).onDozeChanged(eq(true)); + } + // Helpers private void showDialog(int[] sensorIds, boolean credentialAllowed) { @@ -705,10 +734,12 @@ public class AuthControllerTest extends SysuiTestCase { FingerprintManager fingerprintManager, FaceManager faceManager, Provider<UdfpsController> udfpsControllerFactory, - Provider<SidefpsController> sidefpsControllerFactory) { + Provider<SidefpsController> sidefpsControllerFactory, + StatusBarStateController statusBarStateController) { super(context, execution, commandQueue, activityTaskManager, windowManager, fingerprintManager, faceManager, udfpsControllerFactory, - sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle, mHandler); + sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle, + statusBarStateController, mHandler); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 159bdbab6d8d..35e838bfca9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -44,7 +44,6 @@ import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.os.VibrationAttributes; -import android.os.Vibrator; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; @@ -64,6 +63,7 @@ import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -141,7 +141,7 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private ScreenLifecycle mScreenLifecycle; @Mock - private Vibrator mVibrator; + private VibratorHelper mVibrator; @Mock private UdfpsHapticsSimulator mUdfpsHapticsSimulator; @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt index 55509d1ae0bd..9908d44507cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt @@ -20,13 +20,11 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.controls.ControlsMetricsLogger -import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.wm.shell.TaskViewFactory -import dagger.Lazy -import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -41,15 +39,14 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import java.util.Optional @SmallTest @RunWith(AndroidTestingRunner::class) class ControlActionCoordinatorImplTest : SysuiTestCase() { @Mock - private lateinit var uiController: ControlsUiController - @Mock - private lateinit var lazyUiController: Lazy<ControlsUiController> + private lateinit var vibratorHelper: VibratorHelper @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock @@ -59,8 +56,6 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock - private lateinit var globalActionsComponent: GlobalActionsComponent - @Mock private lateinit var taskViewFactory: Optional<TaskViewFactory> @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var cvh: ControlViewHolder @@ -86,11 +81,9 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() { uiExecutor, activityStarter, keyguardStateController, - globalActionsComponent, taskViewFactory, - getFakeBroadcastDispatcher(), - lazyUiController, - metricsLogger + metricsLogger, + vibratorHelper )) `when`(cvh.cws.ci.controlId).thenReturn(ID) diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index b3b5fa509105..d5bd67adcf09 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -91,6 +91,9 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Mock DreamOverlayTouchMonitor mDreamOverlayTouchMonitor; + @Mock + DreamOverlayStateController mStateController; + DreamOverlayService mService; @@ -115,6 +118,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase { mService = new DreamOverlayService(mContext, mMainExecutor, mDreamOverlayComponentFactory, + mStateController, mKeyguardUpdateMonitor); final IBinder proxy = mService.onBind(new Intent()); final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index 7d0833db7ae4..627da3c5ec77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -16,11 +16,15 @@ package com.android.systemui.dreams; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; @@ -35,6 +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 java.util.Collection; @@ -56,7 +61,29 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { } @Test - public void testCallback() throws Exception { + public void testStateChange() { + final DreamOverlayStateController stateController = new DreamOverlayStateController( + mExecutor); + stateController.addCallback(mCallback); + stateController.setOverlayActive(true); + mExecutor.runAllReady(); + + verify(mCallback).onStateChanged(); + assertThat(stateController.isOverlayActive()).isTrue(); + + Mockito.clearInvocations(mCallback); + stateController.setOverlayActive(true); + mExecutor.runAllReady(); + verify(mCallback, never()).onStateChanged(); + + stateController.setOverlayActive(false); + mExecutor.runAllReady(); + verify(mCallback).onStateChanged(); + assertThat(stateController.isOverlayActive()).isFalse(); + } + + @Test + public void testCallback() { final DreamOverlayStateController stateController = new DreamOverlayStateController( mExecutor); stateController.addCallback(mCallback); @@ -94,4 +121,43 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { mExecutor.runAllReady(); verify(mCallback, times(1)).onComplicationsChanged(); } + + @Test + public void testComplicationFiltering() { + final DreamOverlayStateController stateController = + new DreamOverlayStateController(mExecutor); + + final Complication alwaysAvailableComplication = Mockito.mock(Complication.class); + final Complication weatherComplication = Mockito.mock(Complication.class); + when(alwaysAvailableComplication.getRequiredTypeAvailability()) + .thenReturn(Complication.COMPLICATION_TYPE_NONE); + when(weatherComplication.getRequiredTypeAvailability()) + .thenReturn(Complication.COMPLICATION_TYPE_WEATHER); + + stateController.addComplication(alwaysAvailableComplication); + stateController.addComplication(weatherComplication); + + final DreamOverlayStateController.Callback callback = + Mockito.mock(DreamOverlayStateController.Callback.class); + + stateController.addCallback(callback); + mExecutor.runAllReady(); + + { + final Collection<Complication> complications = stateController.getComplications(); + assertThat(complications.contains(alwaysAvailableComplication)).isTrue(); + assertThat(complications.contains(weatherComplication)).isFalse(); + } + + stateController.setAvailableComplicationTypes(Complication.COMPLICATION_TYPE_WEATHER); + mExecutor.runAllReady(); + verify(callback).onAvailableComplicationTypesChanged(); + + { + final Collection<Complication> complications = stateController.getComplications(); + assertThat(complications.contains(alwaysAvailableComplication)).isTrue(); + assertThat(complications.contains(weatherComplication)).isTrue(); + } + + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java new file mode 100644 index 000000000000..3b17a8071cb2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.dreams; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class SmartSpaceComplicationTest extends SysuiTestCase { + @Mock + private Context mContext; + + @Mock + private LockscreenSmartspaceController mSmartspaceController; + + @Mock + private DreamOverlayStateController mDreamOverlayStateController; + + @Mock + private SmartSpaceComplication mComplication; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Ensures {@link SmartSpaceComplication} is only registered when it is available. + */ + @Test + public void testAvailability() { + when(mSmartspaceController.isEnabled()).thenReturn(false); + + final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant( + mContext, + mDreamOverlayStateController, + mComplication, + mSmartspaceController); + registrant.start(); + verify(mDreamOverlayStateController, never()).addComplication(any()); + + when(mSmartspaceController.isEnabled()).thenReturn(true); + registrant.start(); + verify(mDreamOverlayStateController).addComplication(eq(mComplication)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java index feeea5dff7ea..5fcf414f0251 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java @@ -75,6 +75,10 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase { callbackCaptor.getValue().onComplicationsChanged(); verifyUpdate(observer, complications); + + callbackCaptor.getValue().onAvailableComplicationTypesChanged(); + + verifyUpdate(observer, complications); }); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java index f227a9b78c39..d5ab708f893b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java @@ -27,6 +27,7 @@ import android.view.View; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.test.filters.SmallTest; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import org.junit.Before; @@ -122,6 +123,34 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { } /** + * Makes sure the engine properly places a view within the {@link ConstraintLayout}. + */ + @Test + public void testSnapToGuide() { + final ViewInfo firstViewInfo = new ViewInfo( + new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP + | ComplicationLayoutParams.POSITION_END, + ComplicationLayoutParams.DIRECTION_DOWN, + 0, + true), + Complication.CATEGORY_STANDARD, + mLayout); + + final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout); + addComplication(engine, firstViewInfo); + + // Ensure the view is added to the top end corner + verifyChange(firstViewInfo, true, lp -> { + assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); + assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue(); + assertThat(lp.startToEnd == R.id.complication_end_guide).isTrue(); + }); + } + + /** * Ensures layout in a particular direction updates. */ @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java new file mode 100644 index 000000000000..f1978b214594 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.complication; + +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_AIR_QUALITY; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER; +import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationType; + + +import static com.google.common.truth.Truth.assertThat; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.settingslib.dream.DreamBackend; +import com.android.systemui.SysuiTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ComplicationUtilsTest extends SysuiTestCase { + @Test + public void testConvertComplicationType() { + assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_TIME)) + .isEqualTo(COMPLICATION_TYPE_TIME); + assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_DATE)) + .isEqualTo(COMPLICATION_TYPE_DATE); + assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_WEATHER)) + .isEqualTo(COMPLICATION_TYPE_WEATHER); + assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_AIR_QUALITY)) + .isEqualTo(COMPLICATION_TYPE_AIR_QUALITY); + assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_CAST_INFO)) + .isEqualTo(COMPLICATION_TYPE_CAST_INFO); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java new file mode 100644 index 000000000000..b02c506be8be --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockDateComplicationTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.dreams.complication; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.DreamOverlayStateController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class DreamClockDateComplicationTest extends SysuiTestCase { + @SuppressWarnings("HidingField") + @Mock + private Context mContext; + + @Mock + private DreamOverlayStateController mDreamOverlayStateController; + + @Mock + private DreamClockDateComplication mComplication; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Ensures {@link DreamClockDateComplication} is registered. + */ + @Test + public void testComplicationAdded() { + final DreamClockDateComplication.Registrant registrant = + new DreamClockDateComplication.Registrant( + mContext, + mDreamOverlayStateController, + mComplication); + registrant.start(); + verify(mDreamOverlayStateController).addComplication(eq(mComplication)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java new file mode 100644 index 000000000000..088b4d5136ff --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.dreams.complication; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.DreamOverlayStateController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class DreamClockTimeComplicationTest extends SysuiTestCase { + @SuppressWarnings("HidingField") + @Mock + private Context mContext; + + @Mock + private DreamOverlayStateController mDreamOverlayStateController; + + @Mock + private DreamClockTimeComplication mComplication; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Ensures {@link DreamClockTimeComplication} is registered. + */ + @Test + public void testComplicationAdded() { + final DreamClockTimeComplication.Registrant registrant = + new DreamClockTimeComplication.Registrant( + mContext, + mDreamOverlayStateController, + mComplication); + registrant.start(); + verify(mDreamOverlayStateController).addComplication(eq(mComplication)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java new file mode 100644 index 000000000000..151742af7e1a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.dreams.complication; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class DreamWeatherComplicationTest extends SysuiTestCase { + @SuppressWarnings("HidingField") + @Mock + private Context mContext; + + @Mock + private LockscreenSmartspaceController mSmartspaceController; + + @Mock + private DreamOverlayStateController mDreamOverlayStateController; + + @Mock + private DreamWeatherComplication mComplication; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + /** + * Ensures {@link DreamWeatherComplication} is only registered when it is available. + */ + @Test + public void testComplicationAvailability() { + when(mSmartspaceController.isEnabled()).thenReturn(false); + final DreamWeatherComplication.Registrant registrant = + new DreamWeatherComplication.Registrant( + mContext, + mSmartspaceController, + mDreamOverlayStateController, + mComplication); + registrant.start(); + verify(mDreamOverlayStateController, never()).addComplication(any()); + + when(mSmartspaceController.isEnabled()).thenReturn(true); + registrant.start(); + verify(mDreamOverlayStateController).addComplication(eq(mComplication)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java new file mode 100644 index 000000000000..1a8326fd5bd1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.dreams.touch; + +import static com.google.common.truth.Truth.assertThat; + + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.animation.ValueAnimator; +import android.testing.AndroidTestingRunner; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.VelocityTracker; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.shared.system.InputChannelCompat; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.KeyguardBouncer; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.wm.shell.animation.FlingAnimationUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.Random; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { + @Mock + StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + + @Mock + StatusBar mStatusBar; + + @Mock + NotificationShadeWindowController mNotificationShadeWindowController; + + @Mock + FlingAnimationUtils mFlingAnimationUtils; + + + @Mock + FlingAnimationUtils mFlingAnimationUtilsClosing; + + @Mock + DreamTouchHandler.TouchSession mTouchSession; + + BouncerSwipeTouchHandler mTouchHandler; + + @Mock + BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator; + + @Mock + ValueAnimator mValueAnimator; + + @Mock + BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory; + + @Mock + VelocityTracker mVelocityTracker; + + private static final float TOUCH_REGION = .3f; + private static final float SCREEN_HEIGHT_PX = 100; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mTouchHandler = new BouncerSwipeTouchHandler( + mStatusBarKeyguardViewManager, + mStatusBar, + mNotificationShadeWindowController, + mValueAnimatorCreator, + mVelocityTrackerFactory, + mFlingAnimationUtils, + mFlingAnimationUtilsClosing, + TOUCH_REGION); + when(mStatusBar.getDisplayHeight()).thenReturn(SCREEN_HEIGHT_PX); + when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator); + when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker); + } + + private static void beginValidSwipe(GestureDetector.OnGestureListener listener) { + listener.onDown(MotionEvent.obtain(0, 0, + MotionEvent.ACTION_DOWN, 0, + SCREEN_HEIGHT_PX - (.5f * TOUCH_REGION * SCREEN_HEIGHT_PX), 0)); + } + + /** + * Ensures expansion only happens when touch down happens in valid part of the screen. + */ + @Test + public void testSessionStart() { + mTouchHandler.onSessionStart(mTouchSession); + verify(mNotificationShadeWindowController).setForcePluginOpen(eq(true), any()); + ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor = + ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class); + ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = + ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); + verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture()); + verify(mTouchSession).registerInputListener(eventListenerCaptor.capture()); + + final Random random = new Random(System.currentTimeMillis()); + + // If an initial touch down meeting criteria has been met, scroll behavior should be + // ignored. + assertThat(gestureListenerCaptor.getValue() + .onScroll(Mockito.mock(MotionEvent.class), + Mockito.mock(MotionEvent.class), + random.nextFloat(), + random.nextFloat())).isFalse(); + + // A touch at the top of the screen should also not trigger listening. + final MotionEvent touchDownEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, + 0, 0, 0); + + gestureListenerCaptor.getValue().onDown(touchDownEvent); + assertThat(gestureListenerCaptor.getValue() + .onScroll(Mockito.mock(MotionEvent.class), + Mockito.mock(MotionEvent.class), + random.nextFloat(), + random.nextFloat())).isFalse(); + + // A touch within range at the bottom of the screen should trigger listening + beginValidSwipe(gestureListenerCaptor.getValue()); + assertThat(gestureListenerCaptor.getValue() + .onScroll(Mockito.mock(MotionEvent.class), + Mockito.mock(MotionEvent.class), + random.nextFloat(), + random.nextFloat())).isTrue(); + } + + /** + * Makes sure expansion amount is proportional to scroll. + */ + @Test + public void testExpansionAmount() { + mTouchHandler.onSessionStart(mTouchSession); + ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = + ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); + verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture()); + + beginValidSwipe(gestureListenerCaptor.getValue()); + + final float scrollAmount = .3f; + final float distanceY = SCREEN_HEIGHT_PX * scrollAmount; + + final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, + 0, SCREEN_HEIGHT_PX, 0); + final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, + 0, SCREEN_HEIGHT_PX - distanceY, 0); + + assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY)) + .isTrue(); + + // Ensure only called once + verify(mStatusBarKeyguardViewManager) + .onPanelExpansionChanged(anyFloat(), anyBoolean(), anyBoolean()); + + // Ensure correct expansion passed in. + verify(mStatusBarKeyguardViewManager) + .onPanelExpansionChanged(eq(1 - scrollAmount), eq(false), eq(true)); + } + + private void swipeToPosition(float position, float velocityY) { + mTouchHandler.onSessionStart(mTouchSession); + ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = + ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); + ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor = + ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class); + verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture()); + verify(mTouchSession).registerInputListener(inputEventListenerCaptor.capture()); + + when(mVelocityTracker.getYVelocity()).thenReturn(velocityY); + + beginValidSwipe(gestureListenerCaptor.getValue()); + + final float distanceY = SCREEN_HEIGHT_PX * position; + + final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, + 0, SCREEN_HEIGHT_PX, 0); + final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, + 0, SCREEN_HEIGHT_PX - distanceY, 0); + + assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0 , distanceY)) + .isTrue(); + + final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, + 0, 0, 0); + + inputEventListenerCaptor.getValue().onInputEvent(upEvent); + } + + /** + * Tests that ending a swipe before the set expansion threshold leads to bouncer collapsing + * down. + */ + @Test + public void testCollapseOnThreshold() { + final float swipeUpPercentage = .3f; + swipeToPosition(swipeUpPercentage, -1); + + verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage), + eq(KeyguardBouncer.EXPANSION_VISIBLE)); + verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator), anyFloat(), anyFloat(), + anyFloat(), anyFloat()); + verify(mValueAnimator).start(); + } + + /** + * Tests that ending a swipe above the set expansion threshold will continue the expansion. + */ + @Test + public void testExpandOnThreshold() { + final float swipeUpPercentage = .7f; + swipeToPosition(swipeUpPercentage, 1); + + verify(mValueAnimatorCreator).create(eq(1 - swipeUpPercentage), + eq(KeyguardBouncer.EXPANSION_HIDDEN)); + verify(mFlingAnimationUtils).apply(eq(mValueAnimator), anyFloat(), anyFloat(), + anyFloat(), anyFloat()); + verify(mValueAnimator).start(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index e3a7e3b43b77..71fc8ee6cce8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -61,6 +61,7 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -115,6 +116,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private PackageManager mPackageManager; @Mock private Handler mHandler; @Mock private UserContextProvider mUserContextProvider; + @Mock private VibratorHelper mVibratorHelper; @Mock private StatusBar mStatusBar; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; @@ -143,7 +145,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mTelephonyListenerManager, mGlobalSettings, mSecureSettings, - null, + mVibratorHelper, mResources, mConfigurationController, mKeyguardStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java new file mode 100644 index 000000000000..1171bd299c1f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/hdmi/HdmiCecSetMenuLanguageHelperTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.hdmi; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.provider.Settings; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.settings.SecureSettings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Locale; +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class HdmiCecSetMenuLanguageHelperTest extends SysuiTestCase { + + private HdmiCecSetMenuLanguageHelper mHdmiCecSetMenuLanguageHelper; + + @Mock + private Executor mExecutor; + + @Mock + private SecureSettings mSecureSettings; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mSecureSettings.getString( + Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST)).thenReturn(null); + mHdmiCecSetMenuLanguageHelper = + new HdmiCecSetMenuLanguageHelper(mExecutor, mSecureSettings); + } + + @Test + public void testSetGetLocale() { + mHdmiCecSetMenuLanguageHelper.setLocale("en"); + assertThat(mHdmiCecSetMenuLanguageHelper.getLocale()).isEqualTo(Locale.ENGLISH); + } + + @Test + public void testIsLocaleDenylisted_EmptyByDefault() { + mHdmiCecSetMenuLanguageHelper.setLocale("en"); + assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false); + } + + @Test + public void testIsLocaleDenylisted_AcceptLanguage() { + mHdmiCecSetMenuLanguageHelper.setLocale("de"); + mHdmiCecSetMenuLanguageHelper.acceptLocale(); + assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(false); + verify(mExecutor).execute(any()); + } + + @Test + public void testIsLocaleDenylisted_DeclineLanguage() { + mHdmiCecSetMenuLanguageHelper.setLocale("de"); + mHdmiCecSetMenuLanguageHelper.declineLocale(); + assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true); + verify(mSecureSettings).putString( + Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de"); + } + + @Test + public void testIsLocaleDenylisted_DeclineTwoLanguages() { + mHdmiCecSetMenuLanguageHelper.setLocale("de"); + mHdmiCecSetMenuLanguageHelper.declineLocale(); + assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true); + verify(mSecureSettings).putString( + Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de"); + mHdmiCecSetMenuLanguageHelper.setLocale("pl"); + mHdmiCecSetMenuLanguageHelper.declineLocale(); + assertThat(mHdmiCecSetMenuLanguageHelper.isLocaleDenylisted()).isEqualTo(true); + verify(mSecureSettings).putString( + Settings.Secure.HDMI_CEC_SET_MENU_LANGUAGE_DENYLIST, "de,pl"); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index da8ab27d7e3d..d94e2eee9ffa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -48,6 +48,7 @@ import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollectorFake; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.statusbar.NotificationShadeDepthController; @@ -98,6 +99,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock InteractionJankMonitor mInteractionJankMonitor; private @Mock ScreenOnCoordinator mScreenOnCoordinator; private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy; + private @Mock DreamOverlayStateController mDreamOverlayStateController; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -202,6 +204,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mNotificationShadeDepthController, mScreenOnCoordinator, mInteractionJankMonitor, + mDreamOverlayStateController, mNotificationShadeWindowControllerLazy); mViewMediator.start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index d7c00fbe1e85..5ed1d656a1f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -38,7 +38,6 @@ import android.graphics.drawable.AnimatedStateListDrawable; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.SensorLocationInternal; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.os.Vibrator; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Pair; @@ -62,6 +61,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -104,7 +104,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { private @Mock DumpManager mDumpManager; private @Mock AccessibilityManager mAccessibilityManager; private @Mock ConfigurationController mConfigurationController; - private @Mock Vibrator mVibrator; + private @Mock VibratorHelper mVibrator; private @Mock AuthRippleController mAuthRippleController; private FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt index a3ffb2fe4b8d..97b3b10ebcbf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt @@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq +import com.android.systemui.dreams.DreamOverlayStateController import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -85,6 +86,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private lateinit var configurationController: ConfigurationController @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView + @Mock + private lateinit var dreamOverlayStateController: DreamOverlayStateController @Captor private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)> @Captor @@ -110,7 +113,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() { notificationLockscreenUserManager, configurationController, wakefulnessLifecycle, - statusBarKeyguardViewManager) + statusBarKeyguardViewManager, + dreamOverlayStateController) verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) verify(statusBarStateController).addCallback(statusBarCallback.capture()) setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java new file mode 100644 index 000000000000..29188da46562 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dream; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.widget.FrameLayout; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.media.MediaHost; +import com.android.systemui.util.animation.UniqueObjectHostView; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class MediaComplicationViewControllerTest extends SysuiTestCase { + @Mock + private MediaHost mMediaHost; + + @Mock + private UniqueObjectHostView mView; + + @Mock + private FrameLayout mComplicationContainer; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mMediaHost.hostView = mView; + } + + @Test + public void testMediaHostViewInteraction() { + final MediaComplicationViewController controller = new MediaComplicationViewController( + mComplicationContainer, mMediaHost); + + controller.init(); + + controller.onViewAttached(); + verify(mComplicationContainer).addView(eq(mView)); + + controller.onViewDetached(); + verify(mComplicationContainer).removeView(eq(mView)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java new file mode 100644 index 000000000000..114fc90e8590 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media.dream; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.media.MediaData; +import com.android.systemui.media.MediaDataManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class MediaDreamSentinelTest extends SysuiTestCase { + @Mock + MediaDataManager mMediaDataManager; + + @Mock + DreamOverlayStateController mDreamOverlayStateController; + + @Mock + MediaDreamComplication mComplication; + + final String mKey = "key"; + final String mOldKey = "old_key"; + + @Mock + MediaData mData; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testComplicationAddition() { + final MediaDreamSentinel sentinel = new MediaDreamSentinel(mContext, mMediaDataManager, + mDreamOverlayStateController, mComplication); + + sentinel.start(); + + ArgumentCaptor<MediaDataManager.Listener> listenerCaptor = + ArgumentCaptor.forClass(MediaDataManager.Listener.class); + verify(mMediaDataManager).addListener(listenerCaptor.capture()); + + final MediaDataManager.Listener listener = listenerCaptor.getValue(); + + when(mMediaDataManager.hasActiveMedia()).thenReturn(false); + listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0); + verify(mDreamOverlayStateController, never()).addComplication(any()); + + when(mMediaDataManager.hasActiveMedia()).thenReturn(true); + listener.onMediaDataLoaded(mKey, mOldKey, mData, true, 0); + verify(mDreamOverlayStateController).addComplication(eq(mComplication)); + + listener.onMediaDataRemoved(mKey); + verify(mDreamOverlayStateController, never()).removeComplication(any()); + + when(mMediaDataManager.hasActiveMedia()).thenReturn(false); + listener.onMediaDataRemoved(mKey); + verify(mDreamOverlayStateController).removeComplication(eq(mComplication)); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt new file mode 100644 index 000000000000..32314159f865 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt @@ -0,0 +1,117 @@ +package com.android.systemui.shared.animation + +import android.testing.AndroidTestingRunner +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction +import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate +import com.android.systemui.unfold.UnfoldTransitionProgressProvider +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() { + + @Mock private lateinit var progressProvider: UnfoldTransitionProgressProvider + + @Mock private lateinit var parent: ViewGroup + + @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener> + + private lateinit var animator: UnfoldConstantTranslateAnimator + private lateinit var progressListener: TransitionProgressListener + + private val viewsIdToRegister = + setOf( + ViewIdToTranslate(LEFT_VIEW_ID, Direction.LEFT), + ViewIdToTranslate(RIGHT_VIEW_ID, Direction.RIGHT)) + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + animator = + UnfoldConstantTranslateAnimator(viewsIdToRegister, progressProvider) + + animator.init(parent, MAX_TRANSLATION) + + verify(progressProvider).addCallback(progressListenerCaptor.capture()) + progressListener = progressListenerCaptor.value + } + + @Test + fun onTransition_noMatchingIds() { + // GIVEN no views matching any ids + // WHEN the transition starts + progressListener.onTransitionStarted() + progressListener.onTransitionProgress(.1f) + + // THEN nothing... no exceptions + } + + @Test + fun onTransition_oneMovesLeft() { + // GIVEN one view with a matching id + val view = View(context) + whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(view) + + moveAndValidate(listOf(view to LEFT)) + } + + @Test + fun onTransition_oneMovesLeftAndOneMovesRightMultipleTimes() { + // GIVEN two views with a matching id + val leftView = View(context) + val rightView = View(context) + whenever(parent.findViewById<View>(LEFT_VIEW_ID)).thenReturn(leftView) + whenever(parent.findViewById<View>(RIGHT_VIEW_ID)).thenReturn(rightView) + + moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT)) + moveAndValidate(listOf(leftView to LEFT, rightView to RIGHT)) + } + + private fun moveAndValidate(list: List<Pair<View, Int>>) { + // Compare values as ints because -0f != 0f + + // WHEN the transition starts + progressListener.onTransitionStarted() + progressListener.onTransitionProgress(0f) + + list.forEach { (view, direction) -> + assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt()) + } + + // WHEN the transition progresses, translation is updated + progressListener.onTransitionProgress(.5f) + list.forEach { (view, direction) -> + assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt()) + } + + // WHEN the transition ends, translation is completed + progressListener.onTransitionProgress(1f) + progressListener.onTransitionFinished() + list.forEach { (view, _) -> assertEquals(0, view.translationX.toInt()) } + } + + companion object { + private val LEFT = Direction.LEFT.multiplier.toInt() + private val RIGHT = Direction.RIGHT.multiplier.toInt() + + private const val MAX_TRANSLATION = 42f + + private const val LEFT_VIEW_ID = 1 + private const val RIGHT_VIEW_ID = 2 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 42647f7b026a..d51d370eecb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -227,7 +227,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { fun testDragDownAmountDoesntCallOutInLockedDownShade() { whenever(nsslController.isInLockedDownShade).thenReturn(true) transitionController.dragDownAmount = 10f - verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat()) + verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat()) verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat()) verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat()) verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(), @@ -238,7 +238,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Test fun testDragDownAmountCallsOut() { transitionController.dragDownAmount = 10f - verify(nsslController).setTransitionToFullShadeAmount(anyFloat()) + verify(nsslController).setTransitionToFullShadeAmount(anyFloat(), anyFloat()) verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat()) verify(scrimController).setTransitionToFullShadeProgress(anyFloat()) verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 83f1d87133c6..7fafb24317b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -35,6 +35,7 @@ import android.widget.LinearLayout; import androidx.test.filters.SmallTest; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; @@ -56,6 +57,7 @@ import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.wm.shell.bubbles.Bubbles; import com.google.android.collect.Lists; @@ -123,7 +125,9 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { mock(DynamicChildBindController.class), mock(LowPriorityInflationHelper.class), mock(AssistantFeedbackController.class), - mNotifPipelineFlags); + mNotifPipelineFlags, + mock(KeyguardUpdateMonitor.class), + mock(KeyguardStateController.class)); mViewHierarchyManager.setUpWithPresenter(mPresenter, mStackController, mListContainer); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt new file mode 100644 index 000000000000..ad908e7f8000 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt @@ -0,0 +1,93 @@ +package com.android.systemui.statusbar + +import android.media.AudioAttributes +import android.os.UserHandle +import android.os.VibrationAttributes +import android.os.VibrationEffect +import android.os.Vibrator +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.eq +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` as whenever +import org.mockito.Mockito.any +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import java.util.concurrent.Executor + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class VibratorHelperTest : SysuiTestCase() { + + @JvmField @Rule + var rule = MockitoJUnit.rule() + + @Mock lateinit var vibrator: Vibrator + @Mock lateinit var executor: Executor + @Captor lateinit var backgroundTaskCaptor: ArgumentCaptor<Runnable> + lateinit var vibratorHelper: VibratorHelper + + @Before + fun setup() { + vibratorHelper = VibratorHelper(vibrator, executor) + whenever(vibrator.hasVibrator()).thenReturn(true) + } + + @Test + fun testVibrate() { + vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK) + verifyAsync().vibrate(any(VibrationEffect::class.java), + any(VibrationAttributes::class.java)) + } + + @Test + fun testVibrate2() { + vibratorHelper.vibrate(UserHandle.USER_CURRENT, "package", + mock(VibrationEffect::class.java), "reason", + mock(VibrationAttributes::class.java)) + verifyAsync().vibrate(eq(UserHandle.USER_CURRENT), eq("package"), + any(VibrationEffect::class.java), eq("reason"), + any(VibrationAttributes::class.java)) + } + + @Test + fun testVibrate3() { + vibratorHelper.vibrate(mock(VibrationEffect::class.java), mock(AudioAttributes::class.java)) + verifyAsync().vibrate(any(VibrationEffect::class.java), any(AudioAttributes::class.java)) + } + + @Test + fun testVibrate4() { + vibratorHelper.vibrate(mock(VibrationEffect::class.java)) + verifyAsync().vibrate(any(VibrationEffect::class.java)) + } + + @Test + fun testHasVibrator() { + assertThat(vibratorHelper.hasVibrator()).isTrue() + verify(vibrator).hasVibrator() + } + + @Test + fun testCancel() { + vibratorHelper.cancel() + verifyAsync().cancel() + } + + private fun verifyAsync(): Vibrator { + verify(executor).execute(backgroundTaskCaptor.capture()) + verify(vibrator).hasVibrator() + backgroundTaskCaptor.value.run() + + return verify(vibrator) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt index 5fd4174af164..3f84c161db20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt @@ -19,8 +19,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.os.UserHandle import android.service.notification.StatusBarNotification import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.DynamicPrivacyController import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -28,9 +31,12 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.withArgCaptor +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import org.junit.Test +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -40,9 +46,13 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { val dynamicPrivacyController: DynamicPrivacyController = mock() val lockscreenUserManager: NotificationLockscreenUserManager = mock() val pipeline: NotifPipeline = mock() + val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock() + val statusBarStateController: StatusBarStateController = mock() + val keyguardStateController: KeyguardStateController = mock() val coordinator: SensitiveContentCoordinator = SensitiveContentCoordinatorModule - .provideCoordinator(dynamicPrivacyController, lockscreenUserManager) + .provideCoordinator(dynamicPrivacyController, lockscreenUserManager, + keyguardUpdateMonitor, statusBarStateController, keyguardStateController) @Test fun onDynamicPrivacyChanged_invokeInvalidationListener() { @@ -190,6 +200,28 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { verify(entry.representativeEntry!!).setSensitive(true, true) } + @Test + fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() { + coordinator.attach(pipeline) + val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) + whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD) + whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any())) + .thenReturn(true) + + val entry = fakeNotification(2, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!, never()).setSensitive(any(), any()) + } + private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry { val mockUserHandle = mock<UserHandle>().apply { whenever(identifier).thenReturn(notifUserId) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java index 15ff5551703b..ab712649a90f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.collection.render; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import android.content.Context; import android.testing.AndroidTestingRunner; @@ -138,7 +139,7 @@ public class ShadeViewDifferTest extends SysuiTestCase { } @Test - public void testRemovedGroupsAreKeptTogether() { + public void testRemovedGroupsAreBrokenApart() { // GIVEN a preexisting tree with a group applySpecAndCheck( node(mController1), @@ -154,10 +155,10 @@ public class ShadeViewDifferTest extends SysuiTestCase { node(mController1) ); - // THEN the group children are still attached to their parent - assertEquals(mController2.getView(), mController3.getView().getParent()); - assertEquals(mController2.getView(), mController4.getView().getParent()); - assertEquals(mController2.getView(), mController5.getView().getParent()); + // THEN the group children are no longer attached to their parent + assertNull(mController3.getView().getParent()); + assertNull(mController4.getView().getParent()); + assertNull(mController5.getView().getParent()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt new file mode 100644 index 000000000000..d280f54e32f2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -0,0 +1,153 @@ +package com.android.systemui.statusbar.notification.stack + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.NotificationShelf +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.* +import org.mockito.Mockito.`when` as whenever + +/** + * Tests for {@link NotificationShelf}. + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotificationShelfTest : SysuiTestCase() { + + private val shelf = NotificationShelf(context, /* attrs */ null) + private val shelfState = shelf.viewState as NotificationShelf.ShelfState + private val ambientState = mock(AmbientState::class.java) + + @Before + fun setUp() { + shelf.bind(ambientState, /* hostLayoutController */ null) + shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5) + } + + @Test + fun testShadeWidth_BasedOnFractionToShade() { + setFractionToShade(0f) + setOnLockscreen(true) + + shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10) + assertTrue(shelfState.actualWidth == 10) + + shelf.updateStateWidth(shelfState, /* fraction */ 0.5f, /* shortestWidth */ 10) + assertTrue(shelfState.actualWidth == 20) + + shelf.updateStateWidth(shelfState, /* fraction */ 1f, /* shortestWidth */ 10) + assertTrue(shelfState.actualWidth == 30) + } + + @Test + fun testShelfIsLong_WhenNotOnLockscreen() { + setFractionToShade(0f) + setOnLockscreen(false) + + shelf.updateStateWidth(shelfState, /* fraction */ 0f, /* shortestWidth */ 10) + assertTrue(shelfState.actualWidth == 30) + } + + @Test + fun testX_inViewForClick() { + val isXInView = shelf.isXInView( + /* localX */ 5f, + /* slop */ 5f, + /* left */ 0f, + /* right */ 10f) + assertTrue(isXInView) + } + + @Test + fun testXSlop_inViewForClick() { + val isLeftXSlopInView = shelf.isXInView( + /* localX */ -3f, + /* slop */ 5f, + /* left */ 0f, + /* right */ 10f) + assertTrue(isLeftXSlopInView) + + val isRightXSlopInView = shelf.isXInView( + /* localX */ 13f, + /* slop */ 5f, + /* left */ 0f, + /* right */ 10f) + assertTrue(isRightXSlopInView) + } + + @Test + fun testX_notInViewForClick() { + val isXLeftOfShelfInView = shelf.isXInView( + /* localX */ -10f, + /* slop */ 5f, + /* left */ 0f, + /* right */ 10f) + assertFalse(isXLeftOfShelfInView) + + val isXRightOfShelfInView = shelf.isXInView( + /* localX */ 20f, + /* slop */ 5f, + /* left */ 0f, + /* right */ 10f) + assertFalse(isXRightOfShelfInView) + } + + @Test + fun testY_inViewForClick() { + val isYInView = shelf.isYInView( + /* localY */ 5f, + /* slop */ 5f, + /* top */ 0f, + /* bottom */ 10f) + assertTrue(isYInView) + } + + @Test + fun testYSlop_inViewForClick() { + val isTopYSlopInView = shelf.isYInView( + /* localY */ -3f, + /* slop */ 5f, + /* top */ 0f, + /* bottom */ 10f) + assertTrue(isTopYSlopInView) + + val isBottomYSlopInView = shelf.isYInView( + /* localY */ 13f, + /* slop */ 5f, + /* top */ 0f, + /* bottom */ 10f) + assertTrue(isBottomYSlopInView) + } + + @Test + fun testY_notInViewForClick() { + val isYAboveShelfInView = shelf.isYInView( + /* localY */ -10f, + /* slop */ 5f, + /* top */ 0f, + /* bottom */ 5f) + assertFalse(isYAboveShelfInView) + + val isYBelowShelfInView = shelf.isYInView( + /* localY */ 15f, + /* slop */ 5f, + /* top */ 0f, + /* bottom */ 5f) + assertFalse(isYBelowShelfInView) + } + + private fun setFractionToShade(fraction: Float) { + shelf.setFractionToShade(fraction) + } + + private fun setOnLockscreen(isOnLockscreen: Boolean) { + whenever(ambientState.isOnKeyguard).thenReturn(isOnLockscreen) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index ea681435132e..1da9bbcdb836 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -69,10 +69,9 @@ class StackScrollAlgorithmTest : SysuiTestCase() { stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) - val closeHandleUnderlapHeight = - context.resources.getDimensionPixelSize(R.dimen.close_handle_underlap) - val fullHeight = - ambientState.layoutMaxHeight + closeHandleUnderlapHeight - ambientState.stackY + val marginBottom = + context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom) + val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 8c7d22dde0b7..fb232ba3ac2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -38,6 +38,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableResources; import com.android.internal.logging.MetricsLogger; +import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; @@ -110,6 +111,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock private SessionTracker mSessionTracker; + @Mock + private LatencyTracker mLatencyTracker; private BiometricUnlockController mBiometricUnlockController; @Before @@ -133,7 +136,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mMetricsLogger, mDumpManager, mPowerManager, mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController, - mSessionTracker); + mSessionTracker, mLatencyTracker); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 36a4c1e5ebfc..01e9822e0484 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -23,9 +23,13 @@ import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.os.UserManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; @@ -45,6 +49,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoController; @@ -52,6 +57,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -87,6 +93,12 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { private SysuiStatusBarStateController mStatusBarStateController; @Mock private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider; + @Mock + private UserManager mUserManager; + @Captor + private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor; + @Captor + private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor; private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider; private KeyguardStatusBarView mKeyguardStatusBarView; @@ -101,8 +113,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { allowTestableLooperAsMainThread(); TestableLooper.get(this).runWithLooper(() -> { mKeyguardStatusBarView = - (KeyguardStatusBarView) LayoutInflater.from(mContext) - .inflate(R.layout.keyguard_status_bar, null); + spy((KeyguardStatusBarView) LayoutInflater.from(mContext) + .inflate(R.layout.keyguard_status_bar, null)); }); mController = new KeyguardStatusBarViewController( @@ -121,7 +133,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor, mBiometricUnlockController, mStatusBarStateController, - mStatusBarContentInsetsProvider + mStatusBarContentInsetsProvider, + mUserManager ); } @@ -133,6 +146,31 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { verify(mAnimationScheduler).addCallback(any()); verify(mUserInfoController).addCallback(any()); verify(mStatusBarIconController).addIconGroup(any()); + verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); + } + + @Test + public void onConfigurationChanged_updatesUserSwitcherVisibility() { + mController.onViewAttached(); + verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture()); + clearInvocations(mUserManager); + clearInvocations(mKeyguardStatusBarView); + + mConfigurationListenerCaptor.getValue().onConfigChanged(null); + verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); + verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean()); + } + + @Test + public void onKeyguardVisibilityChanged_updatesUserSwitcherVisibility() { + mController.onViewAttached(); + verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture()); + clearInvocations(mUserManager); + clearInvocations(mKeyguardStatusBarView); + + mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true); + verify(mUserManager).isUserSwitcherEnabled(anyBoolean()); + verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java index 5d7b15424fec..d002cebe5cb1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java @@ -250,15 +250,17 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { @Notification.GroupAlertBehavior int priorityGroupAlert, @Notification.GroupAlertBehavior int siblingGroupAlert, boolean expectAlertOverride) { + long when = 10000; // Create entries in an order so that the priority entry can be deemed the newest child. NotificationEntry[] siblings = new NotificationEntry[numSiblings]; for (int i = 0; i < numSiblings; i++) { - siblings[i] = mGroupTestHelper.createChildNotification(siblingGroupAlert, i, "sibling"); + siblings[i] = mGroupTestHelper + .createChildNotification(siblingGroupAlert, i, "sibling", ++when); } NotificationEntry priorityEntry = - mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority"); + mGroupTestHelper.createChildNotification(priorityGroupAlert, 0, "priority", ++when); NotificationEntry summaryEntry = - mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary"); + mGroupTestHelper.createSummaryNotification(summaryGroupAlert, 0, "summary", ++when); // The priority entry is an important conversation. when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry))) @@ -322,17 +324,19 @@ public class NotificationGroupManagerLegacyTest extends SysuiTestCase { @Test public void testAlertOverrideWhenUpdatingSummaryAtEnd() { + long when = 10000; int numSiblings = 2; int groupAlert = Notification.GROUP_ALERT_SUMMARY; // Create entries in an order so that the priority entry can be deemed the newest child. NotificationEntry[] siblings = new NotificationEntry[numSiblings]; for (int i = 0; i < numSiblings; i++) { - siblings[i] = mGroupTestHelper.createChildNotification(groupAlert, i, "sibling"); + siblings[i] = + mGroupTestHelper.createChildNotification(groupAlert, i, "sibling", ++when); } NotificationEntry priorityEntry = - mGroupTestHelper.createChildNotification(groupAlert, 0, "priority"); + mGroupTestHelper.createChildNotification(groupAlert, 0, "priority", ++when); NotificationEntry summaryEntry = - mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary"); + mGroupTestHelper.createSummaryNotification(groupAlert, 0, "summary", ++when); // The priority entry is an important conversation. when(mPeopleNotificationIdentifier.getPeopleNotificationType(eq(priorityEntry))) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt index 337e64592750..77e90256ab70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt @@ -215,6 +215,18 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { then(expectedContainerPadding = 0) } + @Test + fun testNotificationsMarginBottomIsUpdated() { + notificationsQSContainerController.splitShadeEnabled = true + verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN) + + whenever(notificationsQSContainer.defaultNotificationsMarginBottom).thenReturn(100) + notificationsQSContainerController.updateMargins() + notificationsQSContainerController.splitShadeEnabled = false + + verify(notificationsQSContainer).setNotificationsMarginBottom(100) + } + private fun given( taskbarVisible: Boolean, navigationMode: Int, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 5d80bca03e03..4758bd4070d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -36,6 +36,7 @@ import android.view.ViewGroup; import androidx.test.filters.SmallTest; +import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardMessageArea; import com.android.keyguard.KeyguardMessageAreaController; @@ -43,6 +44,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.SysuiTestCase; import com.android.systemui.dock.DockManager; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.statusbar.NotificationMediaManager; @@ -97,6 +99,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { private KeyguardMessageArea mKeyguardMessageArea; @Mock private ShadeController mShadeController; + @Mock + private DreamOverlayStateController mDreamOverlayStateController; + @Mock + private LatencyTracker mLatencyTracker; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -116,6 +122,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mStatusBarStateController, mock(ConfigurationController.class), mKeyguardUpdateMonitor, + mDreamOverlayStateController, mock(NavigationModeController.class), mock(DockManager.class), mock(NotificationShadeWindowController.class), @@ -123,7 +130,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mock(NotificationMediaManager.class), mKeyguardBouncerFactory, mKeyguardMessageAreaFactory, - () -> mShadeController); + () -> mShadeController, + mLatencyTracker); mStatusBarKeyguardViewManager.registerStatusBar( mStatusBar, mNotificationPanelView, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index b7c00fe5e3a1..1564dfe8cd06 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -87,6 +87,7 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.demomode.DemoModeController; +import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentService; @@ -285,6 +286,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private NotifLiveDataStore mNotifLiveDataStore; @Mock private InteractionJankMonitor mJankMonitor; @Mock private DeviceStateManager mDeviceStateManager; + @Mock private DreamOverlayStateController mDreamOverlayStateController; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @@ -474,7 +476,8 @@ public class StatusBarTest extends SysuiTestCase { mActivityLaunchAnimator, mNotifPipelineFlags, mJankMonitor, - mDeviceStateManager); + mDeviceStateManager, + mDreamOverlayStateController); when(mKeyguardViewMediator.registerStatusBar( any(StatusBar.class), any(NotificationPanelViewController.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt index fa2a9066d99b..9a7e702152b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt @@ -400,4 +400,16 @@ class UserSwitcherControllerTest : SysuiTestCase() { assertEquals(fgUserName, userSwitcherController.currentUserName) } + + @Test + fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() { + `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM) + assertEquals(true, userSwitcherController.isSystemUser) + } + + @Test + fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() { + `when`(userTracker.userId).thenReturn(1) + assertEquals(false, userSwitcherController.isSystemUser) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java new file mode 100644 index 000000000000..a2fd288ef33e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class PackageObserverTest extends SysuiTestCase { + @Mock + Context mContext; + + @Mock + Observer.Callback mCallback; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testChange() { + final PackageObserver observer = new PackageObserver(mContext, + ComponentName.unflattenFromString("com.foo.bar/baz")); + final ArgumentCaptor<BroadcastReceiver> receiverCapture = + ArgumentCaptor.forClass(BroadcastReceiver.class); + + observer.addCallback(mCallback); + + // Verify broadcast receiver registered. + verify(mContext).registerReceiver(receiverCapture.capture(), any(), anyInt()); + + // Simulate package change. + receiverCapture.getValue().onReceive(mContext, new Intent()); + + // Check that callback was informed. + verify(mCallback).onSourceChanged(); + + observer.removeCallback(mCallback); + + // Make sure receiver is unregistered on last callback removal + verify(mContext).unregisterReceiver(receiverCapture.getValue()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java new file mode 100644 index 000000000000..53d4a96b0640 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.service; + +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class PersistentConnectionManagerTest extends SysuiTestCase { + private static final int MAX_RETRIES = 5; + private static final int RETRY_DELAY_MS = 1000; + private static final int CONNECTION_MIN_DURATION_MS = 5000; + + private FakeSystemClock mFakeClock = new FakeSystemClock(); + private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock); + + @Mock + private ObservableServiceConnection<Proxy> mConnection; + + @Mock + private Observer mObserver; + + private static class Proxy { + } + + private PersistentConnectionManager<Proxy> mConnectionManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + mConnectionManager = new PersistentConnectionManager<>( + mFakeClock, + mFakeExecutor, + mConnection, + MAX_RETRIES, + RETRY_DELAY_MS, + CONNECTION_MIN_DURATION_MS, + mObserver); + } + + private ObservableServiceConnection.Callback<Proxy> captureCallbackAndSend( + ObservableServiceConnection<Proxy> mConnection, Proxy proxy) { + ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor = + ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class); + + verify(mConnection).addCallback(connectionCallbackCaptor.capture()); + verify(mConnection).bind(); + Mockito.clearInvocations(mConnection); + + final ObservableServiceConnection.Callback callback = connectionCallbackCaptor.getValue(); + if (proxy != null) { + callback.onConnected(mConnection, proxy); + } else { + callback.onDisconnected(mConnection, 0); + } + + return callback; + } + + /** + * Validates initial connection. + */ + @Test + public void testConnect() { + mConnectionManager.start(); + captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class)); + } + + /** + * Ensures reconnection on disconnect. + */ + @Test + public void testRetryOnBindFailure() { + mConnectionManager.start(); + ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor = + ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class); + + verify(mConnection).addCallback(connectionCallbackCaptor.capture()); + + // Verify attempts happen. Note that we account for the retries plus initial attempt, which + // is not scheduled. + for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) { + verify(mConnection).bind(); + Mockito.clearInvocations(mConnection); + connectionCallbackCaptor.getValue().onDisconnected(mConnection, 0); + mFakeExecutor.advanceClockToNext(); + mFakeExecutor.runAllReady(); + } + } + + /** + * Ensures rebind on package change. + */ + @Test + public void testAttemptOnPackageChange() { + mConnectionManager.start(); + verify(mConnection).bind(); + ArgumentCaptor<Observer.Callback> callbackCaptor = + ArgumentCaptor.forClass(Observer.Callback.class); + captureCallbackAndSend(mConnection, Mockito.mock(Proxy.class)); + + verify(mObserver).addCallback(callbackCaptor.capture()); + + callbackCaptor.getValue().onSourceChanged(); + verify(mConnection).bind(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index c9462d651bc0..b3805533cabd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -33,7 +33,6 @@ import android.media.IAudioService; import android.media.session.MediaSession; import android.os.Handler; import android.os.Process; -import android.os.Vibrator; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.accessibility.AccessibilityManager; @@ -43,6 +42,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.concurrency.FakeExecutor; @@ -57,8 +57,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Optional; - @RunWith(AndroidTestingRunner.class) @SmallTest @TestableLooper.RunWithLooper @@ -81,7 +79,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { @Mock private NotificationManager mNotificationManager; @Mock - private Vibrator mVibrator; + private VibratorHelper mVibrator; @Mock private IAudioService mIAudioService; @Mock @@ -110,7 +108,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { mThreadFactory.setLooper(TestableLooper.get(this).getLooper()); mVolumeController = new TestableVolumeDialogControllerImpl(mContext, mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager, - mNotificationManager, Optional.of(mVibrator), mIAudioService, mAccessibilityManager, + mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager, mPackageManager, mWakefullnessLifcycle, mCallback); mVolumeController.setEnableDialogs(true, true); } @@ -181,7 +179,7 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { ThreadFactory theadFactory, AudioManager audioManager, NotificationManager notificationManager, - Optional<Vibrator> optionalVibrator, + VibratorHelper optionalVibrator, IAudioService iAudioService, AccessibilityManager accessibilityManager, PackageManager packageManager, diff --git a/services/api/current.txt b/services/api/current.txt index 50f00524676c..dcf7e64479da 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -39,6 +39,7 @@ package com.android.server.am { public interface ActivityManagerLocal { method public boolean canStartForegroundService(int, int, @NonNull String); + method public boolean startAndBindSupplementalProcessService(@NonNull android.content.Intent, @NonNull android.content.ServiceConnection, int) throws android.os.TransactionTooLargeException; } } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 2168fb18888f..a65d5b3b94f7 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -3346,8 +3346,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Isolate the changes relating to RROs. The app info must be copied to prevent // affecting other parts of system server that may have cached this app info. oldAppInfo = new ApplicationInfo(oldAppInfo); - oldAppInfo.overlayPaths = newAppInfo.overlayPaths.clone(); - oldAppInfo.resourceDirs = newAppInfo.resourceDirs.clone(); + oldAppInfo.overlayPaths = newAppInfo.overlayPaths == null + ? null : newAppInfo.overlayPaths.clone(); + oldAppInfo.resourceDirs = newAppInfo.resourceDirs == null + ? null : newAppInfo.resourceDirs.clone(); provider.info.providerInfo.applicationInfo = oldAppInfo; for (int j = 0, M = provider.widgets.size(); j < M; j++) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 095c1fc80d3e..76ee728fdb07 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -17,6 +17,7 @@ package com.android.server.autofill; import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES; +import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE; import static android.service.autofill.FillRequest.FLAG_ACTIVITY_START; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE; @@ -47,13 +48,16 @@ import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityTaskManager; import android.app.IAssistDataReceiver; +import android.app.PendingIntent; import android.app.assist.AssistStructure; import android.app.assist.AssistStructure.AutofillOverlay; import android.app.assist.AssistStructure.ViewNode; +import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.graphics.Bitmap; @@ -148,6 +152,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState AutoFillUI.AutoFillUiCallback, ValueFinder { private static final String TAG = "AutofillSession"; + private static final String ACTION_DELAYED_FILL = + "android.service.autofill.action.DELAYED_FILL"; private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID"; final Object mLock; @@ -155,6 +161,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private final AutofillManagerServiceImpl mService; private final Handler mHandler; private final AutoFillUI mUi; + @NonNull private final Context mContext; private final MetricsLogger mMetricsLogger = new MetricsLogger(); @@ -269,6 +276,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ private boolean mHasCallback; + @GuardedBy("mLock") + private boolean mDelayedFillBroadcastReceiverRegistered; + + @GuardedBy("mLock") + private PendingIntent mDelayedFillPendingIntent; + /** * Extras sent by service on {@code onFillRequest()} calls; the most recent non-null extra is * saved and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls. @@ -356,6 +369,32 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private final AccessibilityManager mAccessibilityManager; + // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a + // new one per Session. + private final BroadcastReceiver mDelayedFillBroadcastReceiver = + new BroadcastReceiver() { + // ErrorProne says mAssistReceiver#processDelayedFillLocked needs to be guarded by + // 'Session.this.mLock', which is the same as mLock. + @SuppressWarnings("GuardedBy") + @Override + public void onReceive(final Context context, final Intent intent) { + if (!intent.getAction().equals(ACTION_DELAYED_FILL)) { + Slog.wtf(TAG, "Unexpected action is received."); + return; + } + if (!intent.hasExtra(EXTRA_REQUEST_ID)) { + Slog.e(TAG, "Delay fill action is missing request id extra."); + return; + } + Slog.v(TAG, "mDelayedFillBroadcastReceiver delayed fill action received"); + synchronized (mLock) { + int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0); + FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE); + mAssistReceiver.processDelayedFillLocked(requestId, response); + } + } + }; + void onSwitchInputMethodLocked() { // One caveat is that for the case where the focus is on a field for which regular autofill // returns null, and augmented autofill is triggered, and then the user switches the input @@ -408,31 +447,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ private final class SessionFlags { /** Whether autofill is disabled by the service */ - @GuardedBy("mLock") private boolean mAutofillDisabled; /** Whether the autofill service supports inline suggestions */ - @GuardedBy("mLock") private boolean mInlineSupportedByService; /** True if session is for augmented only */ - @GuardedBy("mLock") private boolean mAugmentedAutofillOnly; /** Whether the session is currently showing the SaveUi. */ - @GuardedBy("mLock") private boolean mShowingSaveUi; /** Whether the current {@link FillResponse} is expired. */ - @GuardedBy("mLock") private boolean mExpiredResponse; /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */ - @GuardedBy("mLock") private boolean mClientSuggestionsEnabled; /** Whether the fill dialog UI is disabled. */ - @GuardedBy("mLock") private boolean mFillDialogDisabled; } @@ -447,6 +479,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private InlineSuggestionsRequest mPendingInlineSuggestionsRequest; @GuardedBy("mLock") private FillRequest mPendingFillRequest; + @GuardedBy("mLock") + private FillRequest mLastFillRequest; @Nullable Consumer<InlineSuggestionsRequest> newAutofillRequestLocked(ViewState viewState, boolean isInlineRequest) { @@ -473,6 +507,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingInlineSuggestionsRequest = inlineRequest; } + @GuardedBy("mLock") void maybeRequestFillFromServiceLocked() { if (mPendingFillRequest == null) { return; @@ -490,9 +525,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), mPendingFillRequest.getFillContexts(), mPendingFillRequest.getClientState(), - mPendingFillRequest.getFlags(), mPendingInlineSuggestionsRequest); + mPendingFillRequest.getFlags(), + mPendingInlineSuggestionsRequest, + mPendingFillRequest.getDelayedFillIntentSender()); } } + mLastFillRequest = mPendingFillRequest; mRemoteFillService.onFillRequest(mPendingFillRequest); mPendingInlineSuggestionsRequest = null; @@ -594,8 +632,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FillContext> contexts = mergePreviousSessionLocked(/* forSave= */ false); + mDelayedFillPendingIntent = createPendingIntent(requestId); request = new FillRequest(requestId, contexts, mClientState, flags, - /*inlineSuggestionsRequest=*/null); + /*inlineSuggestionsRequest=*/ null, + /*delayedFillIntentSender=*/ mDelayedFillPendingIntent == null + ? null + : mDelayedFillPendingIntent.getIntentSender()); mPendingFillRequest = request; maybeRequestFillFromServiceLocked(); @@ -610,7 +652,70 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void onHandleAssistScreenshot(Bitmap screenshot) { // Do nothing } - }; + + @GuardedBy("mLock") + void processDelayedFillLocked(int requestId, FillResponse response) { + if (mLastFillRequest != null && requestId == mLastFillRequest.getId()) { + Slog.v(TAG, "processDelayedFillLocked: " + + "calling onFillRequestSuccess with new response"); + onFillRequestSuccess(requestId, response, + mService.getServicePackageName(), mLastFillRequest.getFlags()); + } + } + } + + /** Creates {@link PendingIntent} for autofill service to send a delayed fill. */ + private PendingIntent createPendingIntent(int requestId) { + Slog.d(TAG, "createPendingIntent for request " + requestId); + PendingIntent pendingIntent; + final long identity = Binder.clearCallingIdentity(); + try { + Intent intent = new Intent(ACTION_DELAYED_FILL).setPackage("android") + .putExtra(EXTRA_REQUEST_ID, requestId); + pendingIntent = PendingIntent.getBroadcast( + mContext, this.id, intent, + PendingIntent.FLAG_MUTABLE + | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_CANCEL_CURRENT); + } finally { + Binder.restoreCallingIdentity(identity); + } + return pendingIntent; + } + + @GuardedBy("mLock") + private void clearPendingIntentLocked() { + Slog.d(TAG, "clearPendingIntentLocked"); + if (mDelayedFillPendingIntent == null) { + return; + } + final long identity = Binder.clearCallingIdentity(); + try { + mDelayedFillPendingIntent.cancel(); + mDelayedFillPendingIntent = null; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @GuardedBy("mLock") + private void registerDelayedFillBroadcastLocked() { + if (!mDelayedFillBroadcastReceiverRegistered) { + Slog.v(TAG, "registerDelayedFillBroadcastLocked()"); + IntentFilter intentFilter = new IntentFilter(ACTION_DELAYED_FILL); + mContext.registerReceiver(mDelayedFillBroadcastReceiver, intentFilter); + mDelayedFillBroadcastReceiverRegistered = true; + } + } + + @GuardedBy("mLock") + private void unregisterDelayedFillBroadcastLocked() { + if (mDelayedFillBroadcastReceiverRegistered) { + Slog.v(TAG, "unregisterDelayedFillBroadcastLocked()"); + mContext.unregisterReceiver(mDelayedFillBroadcastReceiver); + mDelayedFillBroadcastReceiverRegistered = false; + } + } /** * Returns the ids of all entries in {@link #mViewStates} in the same order. @@ -964,6 +1069,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mHasCallback = hasCallback; mUiLatencyHistory = uiLatencyHistory; mWtfHistory = wtfHistory; + mContext = context; mComponentName = componentName; mCompatMode = compatMode; mSessionState = STATE_ACTIVE; @@ -1096,6 +1202,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState processNullResponseLocked(requestId, requestFlags); return; } + + final int flags = response.getFlags(); + if ((flags & FillResponse.FLAG_DELAY_FILL) != 0) { + Slog.v(TAG, "Service requested to wait for delayed fill response."); + registerDelayedFillBroadcastLocked(); + } } mService.setLastResponse(id, response); @@ -1206,6 +1318,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Nullable CharSequence message) { boolean showMessage = !TextUtils.isEmpty(message); synchronized (mLock) { + unregisterDelayedFillBroadcastLocked(); if (mDestroyed) { Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId + ") rejected - session: " + id + " destroyed"); @@ -1522,7 +1635,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.e(TAG, "Error sending input show up notification", e); } } - synchronized (Session.this.mLock) { + synchronized (mLock) { // stop to show fill dialog mSessionFlags.mFillDialogDisabled = true; } @@ -3259,7 +3372,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private boolean isFillDialogUiEnabled() { // TODO read from Settings or somewhere final boolean isSettingsEnabledFillDialog = true; - synchronized (Session.this.mLock) { + synchronized (mLock) { return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled; } } @@ -3530,6 +3643,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void processNullResponseLocked(int requestId, int flags) { + unregisterDelayedFillBroadcastLocked(); if ((flags & FLAG_MANUAL_REQUEST) != 0) { getUiForShowing().showError(R.string.autofill_error_cannot_autofill, this); } @@ -3744,6 +3858,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // only if handling the current response requires it. mUi.hideAll(this); + if ((newResponse.getFlags() & FillResponse.FLAG_DELAY_FILL) == 0) { + Slog.d(TAG, "Service did not request to wait for delayed fill response."); + unregisterDelayedFillBroadcastLocked(); + } + final int requestId = newResponse.getRequestId(); if (sVerbose) { Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId @@ -4296,6 +4415,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } + clearPendingIntentLocked(); + unregisterDelayedFillBroadcastLocked(); + unlinkClientVultureLocked(); mUi.destroyAll(mPendingSaveUi, this, true); mUi.clearCallback(this); diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java index cda554e9d0cf..3ccabaaea2fa 100644 --- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java +++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java @@ -125,7 +125,7 @@ class AssociationStoreImpl implements AssociationStore { // Update the MacAddress-to-List<Association> map if needed. final MacAddress updatedAddress = updated.getDeviceMacAddress(); final MacAddress currentAddress = current.getDeviceMacAddress(); - macAddressChanged = Objects.equals(currentAddress, updatedAddress); + macAddressChanged = !Objects.equals(currentAddress, updatedAddress); if (macAddressChanged) { if (currentAddress != null) { mAddressMap.get(currentAddress).remove(id); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index cfd37988d234..c3ab2a79e288 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -1255,7 +1255,7 @@ public class CompanionDeviceManagerService extends SystemService } @Override - public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) { + public void onDeviceDisconnected(BluetoothDevice device, int reason) { Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") " + BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason)); CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress()); diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java index 0eb6b8d24768..627b0bebc905 100644 --- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java +++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java @@ -21,8 +21,6 @@ import static android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED; import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE; import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; -import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON; -import static android.bluetooth.BluetoothAdapter.STATE_ON; import static android.bluetooth.BluetoothAdapter.nameForState; import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED; import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED; @@ -156,7 +154,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { private void checkBleState() { enforceInitialized(); - final boolean bleAvailable = isBleAvailable(); + final boolean bleAvailable = mBtAdapter.isLeEnabled(); if (DEBUG) { Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable); } @@ -183,16 +181,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { } } - /** - * A duplicate of {@code BluetoothAdapter.getLeAccess()} method which has the package-private - * access level, so it's not accessible from here. - */ - private boolean isBleAvailable() { - final int state = mBtAdapter.getLeState(); - if (DEBUG) Log.d(TAG, "getLeAccess() state=" + nameForBtState(state)); - return state == STATE_ON || state == STATE_BLE_ON; - } - @MainThread private void startScan() { enforceInitialized(); diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java index dbe866b374f1..93cbe973b00e 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -91,7 +91,7 @@ class BluetoothCompanionDeviceConnectionListener */ @Override public void onDeviceDisconnected(@NonNull BluetoothDevice device, - @DisconnectReason int reason) { + int reason) { if (DEBUG) { Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device)); Log.d(TAG, " reason=" + disconnectReasonText(reason)); diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index 6c56e2f777f3..e6bfd1ff7f1a 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -18,8 +18,11 @@ package com.android.server.companion.virtual; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.StringDef; import android.graphics.Point; import android.graphics.PointF; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; @@ -48,6 +51,20 @@ class InputController { private static final String TAG = "VirtualInputController"; + private static final AtomicLong sNextPhysId = new AtomicLong(1); + + static final String PHYS_TYPE_KEYBOARD = "Keyboard"; + static final String PHYS_TYPE_MOUSE = "Mouse"; + static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen"; + @StringDef(prefix = { "PHYS_TYPE_" }, value = { + PHYS_TYPE_KEYBOARD, + PHYS_TYPE_MOUSE, + PHYS_TYPE_TOUCHSCREEN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface PhysType { + } + private final Object mLock; /* Token -> file descriptor associations. */ @@ -56,6 +73,8 @@ class InputController { final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>(); private final NativeWrapper mNativeWrapper; + private final DisplayManagerInternal mDisplayManagerInternal; + private final InputManagerInternal mInputManagerInternal; /** * Because the pointer is a singleton, it can only be targeted at one display at a time. Because @@ -73,6 +92,8 @@ class InputController { mLock = lock; mNativeWrapper = nativeWrapper; mActivePointerDisplayId = Display.INVALID_DISPLAY; + mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); + mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); } void close() { @@ -90,7 +111,9 @@ class InputController { int productId, @NonNull IBinder deviceToken, int displayId) { - final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId); + final String phys = createPhys(PHYS_TYPE_KEYBOARD); + setUniqueIdAssociation(displayId, phys); + final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys); if (fd < 0) { throw new RuntimeException( "A native error occurred when creating keyboard: " + -fd); @@ -99,7 +122,7 @@ class InputController { synchronized (mLock) { mInputDeviceDescriptors.put(deviceToken, new InputDeviceDescriptor(fd, binderDeathRecipient, - InputDeviceDescriptor.TYPE_KEYBOARD, displayId)); + InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys)); } try { deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); @@ -114,7 +137,9 @@ class InputController { int productId, @NonNull IBinder deviceToken, int displayId) { - final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId); + final String phys = createPhys(PHYS_TYPE_MOUSE); + setUniqueIdAssociation(displayId, phys); + final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys); if (fd < 0) { throw new RuntimeException( "A native error occurred when creating mouse: " + -fd); @@ -123,11 +148,9 @@ class InputController { synchronized (mLock) { mInputDeviceDescriptors.put(deviceToken, new InputDeviceDescriptor(fd, binderDeathRecipient, - InputDeviceDescriptor.TYPE_MOUSE, displayId)); - final InputManagerInternal inputManagerInternal = - LocalServices.getService(InputManagerInternal.class); - inputManagerInternal.setVirtualMousePointerDisplayId(displayId); - inputManagerInternal.setPointerAcceleration(1); + InputDeviceDescriptor.TYPE_MOUSE, displayId, phys)); + mInputManagerInternal.setVirtualMousePointerDisplayId(displayId); + mInputManagerInternal.setPointerAcceleration(1); mActivePointerDisplayId = displayId; } try { @@ -144,7 +167,9 @@ class InputController { @NonNull IBinder deviceToken, int displayId, @NonNull Point screenSize) { - final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, + final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN); + setUniqueIdAssociation(displayId, phys); + final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys, screenSize.y, screenSize.x); if (fd < 0) { throw new RuntimeException( @@ -154,7 +179,7 @@ class InputController { synchronized (mLock) { mInputDeviceDescriptors.put(deviceToken, new InputDeviceDescriptor(fd, binderDeathRecipient, - InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId)); + InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys)); } try { deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); @@ -174,6 +199,7 @@ class InputController { } token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0); mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor()); + InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys()); // Reset values to the default if all virtual mice are unregistered, or set display // id if there's another mouse (choose the most recent). @@ -197,9 +223,7 @@ class InputController { } } if (mostRecentlyCreatedMouse != null) { - final InputManagerInternal inputManagerInternal = - LocalServices.getService(InputManagerInternal.class); - inputManagerInternal.setVirtualMousePointerDisplayId( + mInputManagerInternal.setVirtualMousePointerDisplayId( mostRecentlyCreatedMouse.getDisplayId()); mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId(); } else { @@ -209,14 +233,21 @@ class InputController { } private void resetMouseValuesLocked() { - final InputManagerInternal inputManagerInternal = - LocalServices.getService(InputManagerInternal.class); - inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY); - inputManagerInternal.setPointerAcceleration( + mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY); + mInputManagerInternal.setPointerAcceleration( IInputConstants.DEFAULT_POINTER_ACCELERATION); mActivePointerDisplayId = Display.INVALID_DISPLAY; } + private static String createPhys(@PhysType String type) { + return String.format("virtual%s:%d", type, sNextPhysId.getAndIncrement()); + } + + private void setUniqueIdAssociation(int displayId, String phys) { + final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId; + InputManager.getInstance().addUniqueIdAssociation(phys, displayUniqueId); + } + boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { synchronized (mLock) { final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( @@ -321,17 +352,18 @@ class InputController { fout.println(" creationOrder: " + inputDeviceDescriptor.getCreationOrderNumber()); fout.println(" type: " + inputDeviceDescriptor.getType()); + fout.println(" phys: " + inputDeviceDescriptor.getPhys()); } fout.println(" Active mouse display id: " + mActivePointerDisplayId); } } private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId, - int productId); - private static native int nativeOpenUinputMouse(String deviceName, int vendorId, - int productId); + int productId, String phys); + private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId, + String phys); private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId, - int productId, int height, int width); + int productId, String phys, int height, int width); private static native boolean nativeCloseUinput(int fd); private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action); private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action); @@ -345,20 +377,18 @@ class InputController { /** Wrapper around the static native methods for tests. */ @VisibleForTesting protected static class NativeWrapper { - public int openUinputKeyboard(String deviceName, int vendorId, int productId) { - return nativeOpenUinputKeyboard(deviceName, vendorId, - productId); + public int openUinputKeyboard(String deviceName, int vendorId, int productId, String phys) { + return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys); } - public int openUinputMouse(String deviceName, int vendorId, int productId) { - return nativeOpenUinputMouse(deviceName, vendorId, - productId); + public int openUinputMouse(String deviceName, int vendorId, int productId, String phys) { + return nativeOpenUinputMouse(deviceName, vendorId, productId, phys); } - public int openUinputTouchscreen(String deviceName, int vendorId, int productId, int height, - int width) { - return nativeOpenUinputTouchscreen(deviceName, vendorId, - productId, height, width); + public int openUinputTouchscreen(String deviceName, int vendorId, + int productId, String phys, int height, int width) { + return nativeOpenUinputTouchscreen(deviceName, vendorId, productId, phys, height, + width); } public boolean closeUinput(int fd) { @@ -410,15 +440,17 @@ class InputController { private final IBinder.DeathRecipient mDeathRecipient; private final @Type int mType; private final int mDisplayId; + private final String mPhys; // Monotonically increasing number; devices with lower numbers were created earlier. private final long mCreationOrderNumber; - InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, - @Type int type, int displayId) { + InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type, + int displayId, String phys) { mFd = fd; mDeathRecipient = deathRecipient; mType = type; mDisplayId = displayId; + mPhys = phys; mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement(); } @@ -445,6 +477,10 @@ class InputController { public long getCreationOrderNumber() { return mCreationOrderNumber; } + + public String getPhys() { + return mPhys; + } } private final class BinderDeathRecipient implements IBinder.DeathRecipient { diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index 435d294a3e8e..a35aa7c74ee5 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -21,6 +21,7 @@ import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager.ProcessState; import android.app.usage.UsageStatsManager.StandbyBuckets; import android.content.ComponentName; import android.content.LocusId; @@ -375,10 +376,11 @@ public abstract class UsageStatsManagerInternal { * to this broadcast. * @param timestampMs time (in millis) when the broadcast was dispatched, in * {@link SystemClock#elapsedRealtime()} timebase. + * @param targetUidProcState process state of the uid that the broadcast is targeted to. */ public abstract void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage, @NonNull UserHandle targetUser, long idForResponseEvent, - @ElapsedRealtimeLong long timestampMs); + @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState); /** * Reports a notification posted event to the UsageStatsManager. diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java index e5a7b4e4ee23..8a6b54fd9769 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -19,7 +19,6 @@ package com.android.server; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.media.AudioManager; import android.media.Ringtone; import android.media.RingtoneManager; @@ -32,15 +31,21 @@ import android.os.SystemClock; import android.os.UEventObserver; import android.os.UserHandle; import android.provider.Settings; -import android.util.Log; +import android.util.Pair; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; +import com.android.server.ExtconUEventObserver.ExtconInfo; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * DockObserver monitors for a docking station. @@ -48,9 +53,6 @@ import java.io.PrintWriter; final class DockObserver extends SystemService { private static final String TAG = "DockObserver"; - private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock"; - private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state"; - private static final int MSG_DOCK_STATE_CHANGED = 0; private final PowerManager mPowerManager; @@ -69,6 +71,92 @@ final class DockObserver extends SystemService { private final boolean mAllowTheaterModeWakeFromDock; + private final List<ExtconStateConfig> mExtconStateConfigs; + + static final class ExtconStateProvider { + private final Map<String, String> mState; + + ExtconStateProvider(Map<String, String> state) { + mState = state; + } + + String getValue(String key) { + return mState.get(key); + } + + + static ExtconStateProvider fromString(String stateString) { + Map<String, String> states = new HashMap<>(); + String[] lines = stateString.split("\n"); + for (String line : lines) { + String[] fields = line.split("="); + if (fields.length == 2) { + states.put(fields[0], fields[1]); + } else { + Slog.e(TAG, "Invalid line: " + line); + } + } + return new ExtconStateProvider(states); + } + + static ExtconStateProvider fromFile(String stateFilePath) { + char[] buffer = new char[1024]; + try (FileReader file = new FileReader(stateFilePath)) { + int len = file.read(buffer, 0, 1024); + String stateString = (new String(buffer, 0, len)).trim(); + return ExtconStateProvider.fromString(stateString); + } catch (FileNotFoundException e) { + Slog.w(TAG, "No state file found at: " + stateFilePath); + return new ExtconStateProvider(new HashMap<>()); + } catch (Exception e) { + Slog.e(TAG, "" , e); + return new ExtconStateProvider(new HashMap<>()); + } + } + } + + /** + * Represents a mapping from extcon state to EXTRA_DOCK_STATE value. Each + * instance corresponds to an entry in config_dockExtconStateMapping. + */ + private static final class ExtconStateConfig { + + // The EXTRA_DOCK_STATE that will be used if the extcon key-value pairs match + public final int extraStateValue; + + // A list of key-value pairs that must be present in the extcon state for a match + // to be considered. An empty list is considered a matching wildcard. + public final List<Pair<String, String>> keyValuePairs = new ArrayList<>(); + + ExtconStateConfig(int extraStateValue) { + this.extraStateValue = extraStateValue; + } + } + + private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) { + String[] rows = context.getResources().getStringArray( + com.android.internal.R.array.config_dockExtconStateMapping); + try { + ArrayList<ExtconStateConfig> configs = new ArrayList<>(); + for (String row : rows) { + String[] rowFields = row.split(","); + ExtconStateConfig config = new ExtconStateConfig(Integer.parseInt(rowFields[0])); + for (int i = 1; i < rowFields.length; i++) { + String[] keyValueFields = rowFields[i].split("="); + if (keyValueFields.length != 2) { + throw new IllegalArgumentException("Invalid key-value: " + rowFields[i]); + } + config.keyValuePairs.add(Pair.create(keyValueFields[0], keyValueFields[1])); + } + configs.add(config); + } + return configs; + } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) { + Slog.e(TAG, "Could not parse extcon state config", e); + return new ArrayList<>(); + } + } + public DockObserver(Context context) { super(context); @@ -77,9 +165,25 @@ final class DockObserver extends SystemService { mAllowTheaterModeWakeFromDock = context.getResources().getBoolean( com.android.internal.R.bool.config_allowTheaterModeWakeFromDock); - init(); // set initial status + mExtconStateConfigs = loadExtconStateConfigs(context); + + List<ExtconInfo> infos = ExtconInfo.getExtconInfoForTypes(new String[] { + ExtconInfo.EXTCON_DOCK + }); - mObserver.startObserving(DOCK_UEVENT_MATCH); + if (!infos.isEmpty()) { + ExtconInfo info = infos.get(0); + Slog.i(TAG, "Found extcon info devPath: " + info.getDevicePath() + + ", statePath: " + info.getStatePath()); + + // set initial status + setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath())); + mPreviousDockState = mActualDockState; + + mExtconUEventObserver.startObserving(info); + } else { + Slog.i(TAG, "No extcon dock device found in this kernel."); + } } @Override @@ -101,26 +205,6 @@ final class DockObserver extends SystemService { } } - private void init() { - synchronized (mLock) { - try { - char[] buffer = new char[1024]; - FileReader file = new FileReader(DOCK_STATE_PATH); - try { - int len = file.read(buffer, 0, 1024); - setActualDockStateLocked(Integer.parseInt((new String(buffer, 0, len)).trim())); - mPreviousDockState = mActualDockState; - } finally { - file.close(); - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "This kernel does not have dock station support"); - } catch (Exception e) { - Slog.e(TAG, "" , e); - } - } - } - private void setActualDockStateLocked(int newState) { mActualDockState = newState; if (!mUpdatesStopped) { @@ -234,19 +318,50 @@ final class DockObserver extends SystemService { } }; - private final UEventObserver mObserver = new UEventObserver() { - @Override - public void onUEvent(UEventObserver.UEvent event) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, "Dock UEVENT: " + event.toString()); + private int getDockedStateExtraValue(ExtconStateProvider state) { + for (ExtconStateConfig config : mExtconStateConfigs) { + boolean match = true; + for (Pair<String, String> keyValue : config.keyValuePairs) { + String stateValue = state.getValue(keyValue.first); + match = match && keyValue.second.equals(stateValue); + if (!match) { + break; + } } - try { - synchronized (mLock) { - setActualDockStateLocked(Integer.parseInt(event.get("SWITCH_STATE"))); + if (match) { + return config.extraStateValue; + } + } + + return Intent.EXTRA_DOCK_STATE_DESK; + } + + @VisibleForTesting + void setDockStateFromProviderForTesting(ExtconStateProvider provider) { + synchronized (mLock) { + setDockStateFromProviderLocked(provider); + } + } + + private void setDockStateFromProviderLocked(ExtconStateProvider provider) { + int state = Intent.EXTRA_DOCK_STATE_UNDOCKED; + if ("1".equals(provider.getValue("DOCK"))) { + state = getDockedStateExtraValue(provider); + } + setActualDockStateLocked(state); + } + + private final ExtconUEventObserver mExtconUEventObserver = new ExtconUEventObserver() { + @Override + public void onUEvent(ExtconInfo extconInfo, UEventObserver.UEvent event) { + synchronized (mLock) { + String stateString = event.get("STATE"); + if (stateString != null) { + setDockStateFromProviderLocked(ExtconStateProvider.fromString(stateString)); + } else { + Slog.e(TAG, "Extcon event missing STATE: " + event); } - } catch (NumberFormatException e) { - Slog.e(TAG, "Could not parse switch state from event " + event); } } }; diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index 0bc3fcc83d47..ce30f0348f11 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -576,19 +576,20 @@ public final class SystemServiceManager implements Dumpable { return () -> { final TimingsTraceAndSlog t = new TimingsTraceAndSlog(oldTrace); final String serviceName = service.getClass().getName(); + final int curUserId = curUser.getUserIdentifier(); + t.traceBegin("ssm.on" + USER_STARTING + "User-" + curUserId + "_" + serviceName); try { - final int curUserId = curUser.getUserIdentifier(); - t.traceBegin("ssm.on" + USER_STARTING + "User-" + curUserId + "_" + serviceName); long time = SystemClock.elapsedRealtime(); service.onUserStarting(curUser); warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "on" + USER_STARTING + "User-" + curUserId); - t.traceEnd(); } catch (Exception e) { Slog.wtf(TAG, "Failure reporting " + USER_STARTING + " of user " + curUser + " to service " + serviceName, e); Slog.e(TAG, "Disabling thread pool - please capture a bug report."); sUseLifecycleThreadPool = false; + } finally { + t.traceEnd(); } }; } @@ -601,11 +602,18 @@ public final class SystemServiceManager implements Dumpable { final int curUserId = curUser.getUserIdentifier(); t.traceBegin("ssm.on" + USER_COMPLETED_EVENT + "User-" + curUserId + "_" + eventType + "_" + serviceName); - long time = SystemClock.elapsedRealtime(); - service.onUserCompletedEvent(curUser, eventType); - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, - "on" + USER_COMPLETED_EVENT + "User-" + curUserId); - t.traceEnd(); + try { + long time = SystemClock.elapsedRealtime(); + service.onUserCompletedEvent(curUser, eventType); + warnIfTooLong(SystemClock.elapsedRealtime() - time, service, + "on" + USER_COMPLETED_EVENT + "User-" + curUserId); + } catch (Exception e) { + Slog.wtf(TAG, "Failure reporting " + USER_COMPLETED_EVENT + " of user " + curUser + + " to service " + serviceName, e); + throw e; + } finally { + t.traceEnd(); + } }; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b5c0a67be20f..9353dd832bba 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2721,7 +2721,8 @@ public final class ActiveServices { int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, String resolvedType, final IServiceConnection connection, int flags, - String instanceName, String callingPackage, final int userId) + String instanceName, boolean isSupplementalProcessService, String callingPackage, + final int userId) throws TransactionTooLargeException { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service + " type=" + resolvedType + " conn=" + connection.asBinder() @@ -2805,10 +2806,9 @@ public final class ActiveServices { final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0; final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0; - ServiceLookupResult res = - retrieveServiceLocked(service, instanceName, resolvedType, callingPackage, - callingPid, callingUid, userId, true, - callerFg, isBindExternal, allowInstant); + ServiceLookupResult res = retrieveServiceLocked(service, instanceName, + isSupplementalProcessService, resolvedType, callingPackage, callingPid, callingUid, + userId, true, callerFg, isBindExternal, allowInstant); if (res == null) { return 0; } @@ -3228,6 +3228,20 @@ public final class ActiveServices { int callingPid, int callingUid, int userId, boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, boolean allowInstant) { + return retrieveServiceLocked(service, instanceName, false, resolvedType, callingPackage, + callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal, + allowInstant); + } + + private ServiceLookupResult retrieveServiceLocked(Intent service, + String instanceName, boolean isSupplementalProcessService, String resolvedType, + String callingPackage, int callingPid, int callingUid, int userId, + boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal, + boolean allowInstant) { + if (isSupplementalProcessService && instanceName == null) { + throw new IllegalArgumentException("No instanceName provided for supplemental process"); + } + ServiceRecord r = null; if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "retrieveServiceLocked: " + service + " type=" + resolvedType + " callingUid=" + callingUid); @@ -3249,7 +3263,6 @@ public final class ActiveServices { if (instanceName == null) { comp = service.getComponent(); } else { - // This is for isolated services final ComponentName realComp = service.getComponent(); if (realComp == null) { throw new IllegalArgumentException("Can't use custom instance name '" + instanceName @@ -3304,12 +3317,19 @@ public final class ActiveServices { return null; } if (instanceName != null - && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) { + && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0 + && !isSupplementalProcessService) { throw new IllegalArgumentException("Can't use instance name '" + instanceName - + "' with non-isolated service '" + sInfo.name + "'"); + + "' with non-isolated non-supplemental service '" + sInfo.name + "'"); } - ComponentName className = new ComponentName( - sInfo.applicationInfo.packageName, sInfo.name); + if (isSupplementalProcessService + && (sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) { + throw new IllegalArgumentException("Service cannot be both supplemental and " + + "isolated"); + } + + ComponentName className = new ComponentName(sInfo.applicationInfo.packageName, + sInfo.name); ComponentName name = comp != null ? comp : className; if (!mAm.validateAssociationAllowedLocked(callingPackage, callingUid, name.getPackageName(), sInfo.applicationInfo.uid)) { @@ -3392,7 +3412,8 @@ public final class ActiveServices { = new Intent.FilterComparison(service.cloneFilter()); final ServiceRestarter res = new ServiceRestarter(); r = new ServiceRecord(mAm, className, name, definingPackageName, - definingUid, filter, sInfo, callingFromFg, res); + definingUid, filter, sInfo, callingFromFg, res, + isSupplementalProcessService); res.setService(r); smap.mServicesByInstanceName.put(name, r); smap.mServicesByIntent.put(filter, r); diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java index 9a1bfddcec07..d9ee7d974864 100644 --- a/services/core/java/com/android/server/am/ActivityManagerLocal.java +++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java @@ -18,6 +18,9 @@ package com.android.server.am; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.TransactionTooLargeException; /** * Interface for in-process calls into @@ -58,4 +61,24 @@ public interface ActivityManagerLocal { * @hide */ void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs); + + /** + * Starts a supplemental process service and binds to it. You can through the arguments here + * have the system bring up multiple concurrent processes hosting their own instance of that + * service. The <var>userAppUid</var> you provide here identifies the different instances - each + * unique uid is attributed to a supplemental process. + * + * @param service Identifies the supplemental process service to connect to. The Intent must + * specify an explicit component name. This value cannot be null. + * @param conn Receives information as the service is started and stopped. + * This must be a valid ServiceConnection object; it must not be null. + * @param userAppUid Uid of the app for which the supplemental process needs to be spawned. + * @return {@code true} if the system is in the process of bringing up a + * service that your client has permission to bind to; {@code false} + * if the system couldn't find the service or if your client doesn't + * have permission to bind to it. + */ + boolean startAndBindSupplementalProcessService(@NonNull Intent service, + @NonNull ServiceConnection conn, int userAppUid) throws TransactionTooLargeException; + } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 902659c27818..023d73775ba2 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -218,6 +218,7 @@ import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; import android.content.LocusId; +import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ActivityPresentationInfo; import android.content.pm.ApplicationInfo; @@ -256,6 +257,7 @@ import android.os.BinderProxy; import android.os.BugreportParams; import android.os.Build; import android.os.Bundle; +import android.os.ConditionVariable; import android.os.Debug; import android.os.DropBoxManager; import android.os.FactoryTest; @@ -336,7 +338,7 @@ 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.content.InstallLocationUtils; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; @@ -4951,7 +4953,7 @@ public class ActivityManagerService extends IActivityManager.Stub // This line is needed to CTS test for the correct exception handling // See b/138952436#comment36 for context Slog.i(TAG, "About to commit checkpoint"); - IStorageManager storageManager = PackageHelper.getStorageManager(); + IStorageManager storageManager = InstallLocationUtils.getStorageManager(); storageManager.commitChanges(); } catch (Exception e) { PowerManager pm = (PowerManager) @@ -12314,13 +12316,25 @@ public class ActivityManagerService extends IActivityManager.Stub public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String callingPackage, int userId) throws TransactionTooLargeException { - return bindIsolatedService(caller, token, service, resolvedType, connection, flags, + return bindServiceInstance(caller, token, service, resolvedType, connection, flags, null, callingPackage, userId); } - public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service, + /** + * Binds to a service with a given instanceName, creating it if it does not already exist. + * If the instanceName field is not supplied, binding to the service occurs as usual. + */ + public int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String instanceName, String callingPackage, int userId) throws TransactionTooLargeException { + return bindServiceInstance(caller, token, service, resolvedType, connection, flags, + instanceName, false, callingPackage, userId); + } + + private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service, + String resolvedType, IServiceConnection connection, int flags, String instanceName, + boolean isSupplementalProcessService, String callingPackage, int userId) + throws TransactionTooLargeException { enforceNotIsolatedCaller("bindService"); // Refuse possible leaked file descriptors @@ -12332,6 +12346,10 @@ public class ActivityManagerService extends IActivityManager.Stub throw new IllegalArgumentException("callingPackage cannot be null"); } + if (isSupplementalProcessService && instanceName == null) { + throw new IllegalArgumentException("No instance name provided for isolated process"); + } + // Ensure that instanceName, which is caller provided, does not contain // unusual characters. if (instanceName != null) { @@ -12345,8 +12363,8 @@ public class ActivityManagerService extends IActivityManager.Stub } synchronized(this) { - return mServices.bindServiceLocked(caller, token, service, - resolvedType, connection, flags, instanceName, callingPackage, userId); + return mServices.bindServiceLocked(caller, token, service, resolvedType, connection, + flags, instanceName, isSupplementalProcessService, callingPackage, userId); } } @@ -15478,6 +15496,62 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * Dump the resources structure for the given process + * + * @param process The process to dump resource info for + * @param fd The FileDescriptor to dump it into + * @throws RemoteException + */ + public boolean dumpResources(String process, ParcelFileDescriptor fd, RemoteCallback callback) + throws RemoteException { + synchronized (this) { + ProcessRecord proc = findProcessLOSP(process, UserHandle.USER_CURRENT, "dumpResources"); + IApplicationThread thread; + if (proc == null || (thread = proc.getThread()) == null) { + throw new IllegalArgumentException("Unknown process: " + process); + } + thread.dumpResources(fd, callback); + return true; + } + } + + /** + * Dump the resources structure for all processes + * + * @param fd The FileDescriptor to dump it into + * @throws RemoteException + */ + public void dumpAllResources(ParcelFileDescriptor fd, PrintWriter pw) throws RemoteException { + synchronized (mProcLock) { + mProcessList.forEachLruProcessesLOSP(true, app -> { + ConditionVariable lock = new ConditionVariable(); + RemoteCallback + finishCallback = new RemoteCallback(result -> lock.open(), null); + + pw.println(String.format("------ DUMP RESOURCES %s (%s) ------", + app.processName, + app.info.packageName)); + pw.flush(); + try { + app.getThread().dumpResources(fd.dup(), finishCallback); + lock.block(2000); + } catch (Exception e) { + pw.println(String.format( + "------ EXCEPTION DUMPING RESOURCES for %s (%s): %s ------", + app.processName, + app.info.packageName, + e.getMessage())); + pw.flush(); + } + pw.println(String.format("------ END DUMP RESOURCES %s (%s) ------", + app.processName, + app.info.packageName)); + pw.flush(); + }); + } + } + @Override public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize, String reportPackage) { @@ -15823,6 +15897,34 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public boolean startAndBindSupplementalProcessService(Intent service, + ServiceConnection conn, int userAppUid) throws TransactionTooLargeException { + if (service == null) { + throw new IllegalArgumentException("intent is null"); + } + if (conn == null) { + throw new IllegalArgumentException("connection is null"); + } + if (service.getComponent() == null) { + throw new IllegalArgumentException("service must specify explicit component"); + } + if (!UserHandle.isApp(userAppUid)) { + throw new IllegalArgumentException("uid is not within application range"); + } + + Handler handler = mContext.getMainThreadHandler(); + int flags = Context.BIND_AUTO_CREATE; + + final IServiceConnection sd = mContext.getServiceDispatcher(conn, handler, flags); + service.prepareToLeaveProcess(mContext); + return ActivityManagerService.this.bindServiceInstance( + mContext.getIApplicationThread(), mContext.getActivityToken(), service, + service.resolveTypeIfNeeded(mContext.getContentResolver()), sd, flags, + Integer.toString(userAppUid), /*isSupplementalProcessService*/ true, + mContext.getOpPackageName(), UserHandle.getUserId(userAppUid)) != 0; + } + + @Override public void onUserRemoved(@UserIdInt int userId) { // Clean up any ActivityTaskManager state (by telling it the user is stopped) mAtmInternal.onUserStopped(userId); @@ -15963,6 +16065,23 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * Returns package name by pid. + */ + @Override + @Nullable + public String getPackageNameByPid(int pid) { + synchronized (mPidsSelfLocked) { + final ProcessRecord app = mPidsSelfLocked.get(pid); + + if (app != null && app.info != null) { + return app.info.packageName; + } + + return null; + } + } + + /** * Sets if the given pid has an overlay UI or not. * * @param pid The pid we are setting overlay UI for. diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index c8ad0e88e995..5da461d8e392 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1365,7 +1365,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } public void notePhoneDataConnectionState(final int dataType, final boolean hasData, - final int serviceType) { + final int serviceType, final int nrFrequency) { enforceCallingPermission(); synchronized (mLock) { final long elapsedRealtime = SystemClock.elapsedRealtime(); @@ -1373,7 +1373,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub mHandler.post(() -> { synchronized (mStats) { mStats.notePhoneDataConnectionStateLocked(dataType, hasData, serviceType, - elapsedRealtime, uptime); + nrFrequency, elapsedRealtime, uptime); } }); } @@ -1962,6 +1962,32 @@ public final class BatteryStatsService extends IBatteryStats.Stub } } + /** + * Bluetooth on stat logging + */ + public void noteBluetoothOn(int uid, int reason, String packageName) { + if (Binder.getCallingPid() != Process.myPid()) { + mContext.enforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT, + Binder.getCallingPid(), uid, null); + } + FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED, + uid, null, FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__ENABLED, + reason, packageName); + } + + /** + * Bluetooth off stat logging + */ + public void noteBluetoothOff(int uid, int reason, String packageName) { + if (Binder.getCallingPid() != Process.myPid()) { + mContext.enforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT, + Binder.getCallingPid(), uid, null); + } + FrameworkStatsLog.write_non_chained(FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED, + uid, null, FrameworkStatsLog.BLUETOOTH_ENABLED_STATE_CHANGED__STATE__DISABLED, + reason, packageName); + } + @Override public void noteBleScanStarted(final WorkSource ws, final boolean isUnoptimized) { enforceCallingPermission(); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 0c383ebbbcd7..e2921e9f5e71 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -334,7 +334,7 @@ public final class BroadcastQueue { mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER); // Tell the application to launch this receiver. - maybeReportBroadcastDispatchedEventLocked(r); + maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid); r.intent.setComponent(r.curComponent); boolean started = false; @@ -927,7 +927,7 @@ public final class BroadcastQueue { r.receiverTime = SystemClock.uptimeMillis(); maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r); maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options); - maybeReportBroadcastDispatchedEventLocked(r); + maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid); performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky, r.userId); @@ -1856,7 +1856,7 @@ public final class BroadcastQueue { return null; } - private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r) { + private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) { final String targetPackage = getTargetPackage(r); // Ignore non-explicit broadcasts if (targetPackage == null) { @@ -1867,11 +1867,10 @@ public final class BroadcastQueue { if (r.options == null || r.options.getIdForResponseEvent() <= 0) { return; } - // TODO (206518114): Only report this event when the broadcast is dispatched while the app - // is in the background state. getUsageStatsManagerInternal().reportBroadcastDispatched( r.callingUid, targetPackage, UserHandle.of(r.userId), - r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime()); + r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(), + mService.getUidStateLocked(targetUid)); } @NonNull diff --git a/services/core/java/com/android/server/am/DataConnectionStats.java b/services/core/java/com/android/server/am/DataConnectionStats.java index 6e39a4c802d9..f0910dcb0da2 100644 --- a/services/core/java/com/android/server/am/DataConnectionStats.java +++ b/services/core/java/com/android/server/am/DataConnectionStats.java @@ -109,7 +109,7 @@ public class DataConnectionStats extends BroadcastReceiver { } try { mBatteryStats.notePhoneDataConnectionState(networkType, visible, - mServiceState.getState()); + mServiceState.getState(), mServiceState.getNrFrequencyRange()); } catch (RemoteException e) { Log.w(TAG, "Error noting data connection state", e); } diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 24e7ba4a32b9..da78e2d7504e 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -570,6 +570,14 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN ComponentName instanceName, String definingPackageName, int definingUid, Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg, Runnable restarter) { + this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg, + restarter, false); + } + + ServiceRecord(ActivityManagerService ams, ComponentName name, + ComponentName instanceName, String definingPackageName, int definingUid, + Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg, + Runnable restarter, boolean isSupplementalProcessService) { this.ams = ams; this.name = name; this.instanceName = instanceName; @@ -580,7 +588,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN serviceInfo = sInfo; appInfo = sInfo.applicationInfo; packageName = sInfo.applicationInfo.packageName; - if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) { + if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0 + || isSupplementalProcessService) { processName = sInfo.processName + ":" + instanceName.getClassName(); } else { processName = sInfo.processName; diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 913621913e95..960fbf1d9330 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -50,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.server.wm.WindowManagerInternal; +import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener; import com.android.server.wm.WindowManagerService; import java.util.List; @@ -62,6 +63,18 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private static final int CREATE_GAME_SESSION_TIMEOUT_MS = 10_000; private static final boolean DEBUG = false; + private final TaskSystemBarsListener mTaskSystemBarsVisibilityListener = + new TaskSystemBarsListener() { + @Override + public void onTransientSystemBarsVisibilityChanged( + int taskId, + boolean visible, + boolean wereRevealedFromSwipeOnSystemBar) { + GameServiceProviderInstanceImpl.this.onTransientSystemBarsVisibilityChanged( + taskId, visible, wereRevealedFromSwipeOnSystemBar); + } + }; + private final TaskStackListener mTaskStackListener = new TaskStackListener() { @Override public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException { @@ -98,7 +111,10 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan private final IGameServiceController mGameServiceController = new IGameServiceController.Stub() { @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY) public void createGameSession(int taskId) { + mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY, + "createGameSession()"); mBackgroundExecutor.execute(() -> { GameServiceProviderInstanceImpl.this.createGameSession(taskId); }); @@ -116,9 +132,10 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan }); } - @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) + @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_ACTIVITY) public void restartGame(int taskId) { - mContext.enforceCallingPermission(Manifest.permission.FORCE_STOP_PACKAGES, + mContext.enforceCallingPermission(Manifest.permission.MANAGE_GAME_ACTIVITY, "restartGame()"); mBackgroundExecutor.execute(() -> { GameServiceProviderInstanceImpl.this.restartGame(taskId); @@ -199,6 +216,8 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } catch (RemoteException e) { Slog.w(TAG, "Failed to register task stack listener", e); } + + mWindowManagerInternal.registerTaskSystemBarsListener(mTaskSystemBarsVisibilityListener); } @GuardedBy("mLock") @@ -214,6 +233,9 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan Slog.w(TAG, "Failed to unregister task stack listener", e); } + mWindowManagerInternal.unregisterTaskSystemBarsListener( + mTaskSystemBarsVisibilityListener); + for (GameSessionRecord gameSessionRecord : mGameSessions.values()) { destroyGameSessionFromRecord(gameSessionRecord); } @@ -303,6 +325,37 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } } + private void onTransientSystemBarsVisibilityChanged( + int taskId, + boolean visible, + boolean wereRevealedFromSwipeOnSystemBar) { + if (visible && !wereRevealedFromSwipeOnSystemBar) { + return; + } + + GameSessionRecord gameSessionRecord; + synchronized (mLock) { + gameSessionRecord = mGameSessions.get(taskId); + } + + if (gameSessionRecord == null) { + return; + } + + IGameSession gameSession = gameSessionRecord.getGameSession(); + if (gameSession == null) { + return; + } + + try { + gameSession.onTransientSystemBarVisibilityFromRevealGestureChanged(visible); + } catch (RemoteException ex) { + Slog.w(TAG, + "Failed to send transient system bars visibility from reveal gesture for task: " + + taskId); + } + } + private void createGameSession(int taskId) { synchronized (mLock) { createGameSessionLocked(taskId); @@ -372,12 +425,12 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan }, mBackgroundExecutor); AndroidFuture<Void> unusedPostCreateGameSessionFuture = - mGameSessionServiceConnector.post(gameService -> { + mGameSessionServiceConnector.post(gameSessionService -> { CreateGameSessionRequest createGameSessionRequest = new CreateGameSessionRequest( taskId, existingGameSessionRecord.getComponentName().getPackageName()); - gameService.create( + gameSessionService.create( mGameSessionController, createGameSessionRequest, gameSessionViewHostConfiguration, diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index a1395890fd3c..d2fa386ad6ba 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -22,6 +22,7 @@ import static android.content.Context.BIND_INCLUDE_CAPABILITIES; import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE; import static android.service.attention.AttentionService.ATTENTION_FAILURE_CANCELLED; import static android.service.attention.AttentionService.ATTENTION_FAILURE_UNKNOWN; +import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN; import android.Manifest; import android.annotation.NonNull; @@ -29,6 +30,7 @@ import android.annotation.Nullable; import android.app.ActivityThread; import android.attention.AttentionManagerInternal; import android.attention.AttentionManagerInternal.AttentionCallbackInternal; +import android.attention.AttentionManagerInternal.ProximityCallbackInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -57,6 +59,7 @@ import android.service.attention.AttentionService.AttentionFailureCodes; import android.service.attention.AttentionService.AttentionSuccessCodes; import android.service.attention.IAttentionCallback; import android.service.attention.IAttentionService; +import android.service.attention.IProximityCallback; import android.text.TextUtils; import android.util.Slog; @@ -134,6 +137,15 @@ public class AttentionManagerService extends SystemService { @GuardedBy("mLock") AttentionCheck mCurrentAttentionCheck; + /** + * A proxy for relaying proximity information between the Attention Service and the client. + * The proxy will be initialized when the client calls onStartProximityUpdates and will be + * disabled only when the client calls onStopProximityUpdates. + */ + @VisibleForTesting + @GuardedBy("mLock") + ProximityUpdate mCurrentProximityUpdate; + public AttentionManagerService(Context context) { this(context, (PowerManager) context.getSystemService(Context.POWER_SERVICE), new Object(), null); @@ -315,6 +327,77 @@ public class AttentionManagerService extends SystemService { } } + /** + * Requests the continuous updates of proximity signal via the provided callback, + * until the given callback is stopped. + * + * Calling this multiple times for duplicate requests will be no-ops, returning true. + * + * @return {@code true} if the framework was able to dispatch the request + */ + @VisibleForTesting + boolean onStartProximityUpdates(ProximityCallbackInternal callbackInternal) { + Objects.requireNonNull(callbackInternal); + if (!mIsServiceEnabled) { + Slog.w(LOG_TAG, "Trying to call onProximityUpdate() on an unsupported device."); + return false; + } + + if (!isServiceAvailable()) { + Slog.w(LOG_TAG, "Service is not available at this moment."); + return false; + } + + // don't allow proximity request in screen off state. + // This behavior might change in the future. + if (!mPowerManager.isInteractive()) { + Slog.w(LOG_TAG, "Proximity Service is unavailable during screen off at this moment."); + return false; + } + + synchronized (mLock) { + // schedule shutting down the connection if no one resets this timer + freeIfInactiveLocked(); + + // lazily start the service, which should be very lightweight to start + bindLocked(); + + /* + Prevent spamming with multiple requests, only one at a time is allowed. + If there are use-cases for keeping track of multiple requests, we + can refactor ProximityUpdate object to keep track of multiple internal callbacks. + */ + if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) { + if (mCurrentProximityUpdate.mCallbackInternal == callbackInternal) { + Slog.w(LOG_TAG, "Provided callback is already registered. Skipping."); + return true; + } else { + // reject the new request since the old request is still alive. + Slog.w(LOG_TAG, "New proximity update cannot be processed because there is " + + "already an ongoing update"); + return false; + } + } + mCurrentProximityUpdate = new ProximityUpdate(callbackInternal); + return mCurrentProximityUpdate.startUpdates(); + } + } + + /** Cancels the specified proximity registration. */ + @VisibleForTesting + void onStopProximityUpdates(ProximityCallbackInternal callbackInternal) { + synchronized (mLock) { + if (mCurrentProximityUpdate == null + || !mCurrentProximityUpdate.mCallbackInternal.equals(callbackInternal) + || !mCurrentProximityUpdate.mStartedUpdates) { + Slog.w(LOG_TAG, "Cannot stop a non-current callback"); + return; + } + mCurrentProximityUpdate.cancelUpdates(); + mCurrentProximityUpdate = null; + } + } + @GuardedBy("mLock") @VisibleForTesting protected void freeIfInactiveLocked() { @@ -390,15 +473,18 @@ public class AttentionManagerService extends SystemService { ipw.println("Class=" + mComponentName.getClassName()); ipw.decreaseIndent(); } - ipw.println("binding=" + mBinding); - ipw.println("current attention check:"); synchronized (mLock) { + ipw.println("binding=" + mBinding); + ipw.println("current attention check:"); if (mCurrentAttentionCheck != null) { mCurrentAttentionCheck.dump(ipw); } if (mAttentionCheckCacheBuffer != null) { mAttentionCheckCacheBuffer.dump(ipw); } + if (mCurrentProximityUpdate != null) { + mCurrentProximityUpdate.dump(ipw); + } } } @@ -417,6 +503,17 @@ public class AttentionManagerService extends SystemService { public void cancelAttentionCheck(AttentionCallbackInternal callbackInternal) { AttentionManagerService.this.cancelAttentionCheck(callbackInternal); } + + @Override + public boolean onStartProximityUpdates( + ProximityCallbackInternal callback) { + return AttentionManagerService.this.onStartProximityUpdates(callback); + } + + @Override + public void onStopProximityUpdates(ProximityCallbackInternal callback) { + AttentionManagerService.this.onStopProximityUpdates(callback); + } } @VisibleForTesting @@ -536,6 +633,71 @@ public class AttentionManagerService extends SystemService { } } + @VisibleForTesting + final class ProximityUpdate { + private final ProximityCallbackInternal mCallbackInternal; + private final IProximityCallback mIProximityCallback; + private boolean mStartedUpdates; + + ProximityUpdate(ProximityCallbackInternal callbackInternal) { + mCallbackInternal = callbackInternal; + mIProximityCallback = new IProximityCallback.Stub() { + @Override + public void onProximityUpdate(double distance) { + synchronized (mLock) { + mCallbackInternal.onProximityUpdate(distance); + freeIfInactiveLocked(); + } + } + }; + } + + boolean startUpdates() { + synchronized (mLock) { + if (mStartedUpdates) { + Slog.w(LOG_TAG, "Already registered to a proximity service."); + return false; + } + if (mService == null) { + Slog.w(LOG_TAG, + "There is no service bound. Proximity update request rejected."); + return false; + } + try { + mService.onStartProximityUpdates(mIProximityCallback); + mStartedUpdates = true; + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Cannot call into the AttentionService", e); + return false; + } + } + return true; + } + + void cancelUpdates() { + synchronized (mLock) { + if (mStartedUpdates) { + if (mService == null) { + mStartedUpdates = false; + return; + } + try { + mService.onStopProximityUpdates(); + mStartedUpdates = false; + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Cannot call into the AttentionService", e); + } + } + } + } + + void dump(IndentingPrintWriter ipw) { + ipw.increaseIndent(); + ipw.println("is StartedUpdates=" + mStartedUpdates); + ipw.decreaseIndent(); + } + } + private void appendResultToAttentionCacheBuffer(AttentionCheckCache cache) { synchronized (mLock) { if (mAttentionCheckCacheBuffer == null) { @@ -593,6 +755,18 @@ public class AttentionManagerService extends SystemService { mCurrentAttentionCheck.mCallbackInternal.onFailure(ATTENTION_FAILURE_UNKNOWN); } } + if (mCurrentProximityUpdate != null && mCurrentProximityUpdate.mStartedUpdates) { + if (mService != null) { + try { + mService.onStartProximityUpdates(mCurrentProximityUpdate.mIProximityCallback); + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Cannot call into the AttentionService", e); + } + } else { + mCurrentProximityUpdate.cancelUpdates(); + mCurrentProximityUpdate = null; + } + } } @VisibleForTesting @@ -609,7 +783,9 @@ public class AttentionManagerService extends SystemService { switch (msg.what) { // Do not occupy resources when not in use - unbind proactively. case CHECK_CONNECTION_EXPIRATION: { - cancelAndUnbindLocked(); + synchronized (mLock) { + cancelAndUnbindLocked(); + } } break; @@ -653,10 +829,15 @@ public class AttentionManagerService extends SystemService { @GuardedBy("mLock") private void cancelAndUnbindLocked() { synchronized (mLock) { - if (mCurrentAttentionCheck == null) { + if (mCurrentAttentionCheck == null && mCurrentProximityUpdate == null) { return; } - cancel(); + if (mCurrentAttentionCheck != null) { + cancel(); + } + if (mCurrentProximityUpdate != null) { + mCurrentProximityUpdate.cancelUpdates(); + } if (mService == null) { return; } @@ -702,7 +883,9 @@ public class AttentionManagerService extends SystemService { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { - cancelAndUnbindLocked(); + synchronized (mLock) { + cancelAndUnbindLocked(); + } } } } @@ -730,8 +913,27 @@ public class AttentionManagerService extends SystemService { } } + class TestableProximityCallbackInternal extends ProximityCallbackInternal { + private double mLastCallbackCode = PROXIMITY_UNKNOWN; + + @Override + public void onProximityUpdate(double distance) { + mLastCallbackCode = distance; + } + + public void reset() { + mLastCallbackCode = PROXIMITY_UNKNOWN; + } + + public double getLastCallbackCode() { + return mLastCallbackCode; + } + } + final TestableAttentionCallbackInternal mTestableAttentionCallback = new TestableAttentionCallbackInternal(); + final TestableProximityCallbackInternal mTestableProximityCallback = + new TestableProximityCallbackInternal(); @Override public int onCommand(@Nullable final String cmd) { @@ -749,6 +951,10 @@ public class AttentionManagerService extends SystemService { return cmdCallCheckAttention(); case "cancelCheckAttention": return cmdCallCancelAttention(); + case "onStartProximityUpdates": + return cmdCallOnStartProximityUpdates(); + case "onStopProximityUpdates": + return cmdCallOnStopProximityUpdates(); default: throw new IllegalArgumentException("Invalid argument"); } @@ -758,6 +964,8 @@ public class AttentionManagerService extends SystemService { return cmdClearTestableAttentionService(); case "getLastTestCallbackCode": return cmdGetLastTestCallbackCode(); + case "getLastTestProximityCallbackCode": + return cmdGetLastTestProximityCallbackCode(); default: return handleDefaultCommands(cmd); } @@ -782,6 +990,7 @@ public class AttentionManagerService extends SystemService { private int cmdClearTestableAttentionService() { sTestAttentionServicePackage = ""; mTestableAttentionCallback.reset(); + mTestableProximityCallback.reset(); resetStates(); return 0; } @@ -800,6 +1009,20 @@ public class AttentionManagerService extends SystemService { return 0; } + private int cmdCallOnStartProximityUpdates() { + final PrintWriter out = getOutPrintWriter(); + boolean calledSuccessfully = onStartProximityUpdates(mTestableProximityCallback); + out.println(calledSuccessfully ? "true" : "false"); + return 0; + } + + private int cmdCallOnStopProximityUpdates() { + final PrintWriter out = getOutPrintWriter(); + onStopProximityUpdates(mTestableProximityCallback); + out.println("true"); + return 0; + } + private int cmdResolveAttentionServiceComponent() { final PrintWriter out = getOutPrintWriter(); ComponentName resolvedComponent = resolveAttentionService(mContext); @@ -813,7 +1036,16 @@ public class AttentionManagerService extends SystemService { return 0; } + private int cmdGetLastTestProximityCallbackCode() { + final PrintWriter out = getOutPrintWriter(); + out.println(mTestableProximityCallback.getLastCallbackCode()); + return 0; + } + private void resetStates() { + synchronized (mLock) { + mCurrentProximityUpdate = null; + } mComponentName = resolveAttentionService(mContext); } @@ -844,11 +1076,24 @@ public class AttentionManagerService extends SystemService { + " (to see the result, call getLastTestCallbackCode)"); out.println(" := false, otherwise"); out.println(" call cancelCheckAttention: Cancels check attention"); + out.println(" call onStartProximityUpdates: Calls onStartProximityUpdates"); + out.println(" ---returns:"); + out.println( + " := true, if the request was successfully dispatched to the service " + + "implementation." + + " (to see the result, call getLastTestProximityCallbackCode)"); + out.println(" := false, otherwise"); + out.println(" call onStopProximityUpdates: Cancels proximity updates"); out.println(" getLastTestCallbackCode"); out.println(" ---returns:"); out.println( " := An integer, representing the last callback code received from the " + "bounded implementation. If none, it will return -1"); + out.println(" getLastTestProximityCallbackCode"); + out.println(" ---returns:"); + out.println( + " := A double, representing the last proximity value received from the " + + "bounded implementation. If none, it will return -1.0"); } } diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 42fca9b840df..47f31d505867 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -486,9 +486,7 @@ public class BtHelper { return; } final BluetoothDevice btDevice = deviceList.get(0); - final @BluetoothProfile.BtProfileState int state = - proxy.getConnectionState(btDevice); - if (state == BluetoothProfile.STATE_CONNECTED) { + if (proxy.getConnectionState(btDevice) == BluetoothProfile.STATE_CONNECTED) { mDeviceBroker.queueOnBluetoothActiveDeviceChanged( new AudioDeviceBroker.BtDeviceChangedData(btDevice, null, new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener")); diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index 0da6a1ba3109..79705a32c264 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -53,6 +53,7 @@ import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; +import android.hardware.biometrics.common.OperationContext; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -64,6 +65,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.FrameworkStatsLog; +import com.android.server.biometrics.log.BiometricFrameworkStatsLogger; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -681,16 +683,18 @@ public final class AuthSession implements IBinder.DeathRecipient { + ", Latency: " + latency); } - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED, + final OperationContext operationContext = new OperationContext(); + operationContext.isCrypto = isCrypto(); + BiometricFrameworkStatsLogger.getInstance().authenticate( + operationContext, statsModality(), - mUserId, - isCrypto(), + BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, - mPreAuthInfo.confirmationRequested, - FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED, - latency, mDebugEnabled, - -1 /* sensorId */, + latency, + FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED, + mPreAuthInfo.confirmationRequested, + mUserId, -1f /* ambientLightLux */); } else { final long latency = System.currentTimeMillis() - mStartTimeMs; @@ -711,17 +715,18 @@ public final class AuthSession implements IBinder.DeathRecipient { + ", Latency: " + latency); } // Auth canceled - FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED, + final OperationContext operationContext = new OperationContext(); + operationContext.isCrypto = isCrypto(); + BiometricFrameworkStatsLogger.getInstance().error( + operationContext, statsModality(), - mUserId, - isCrypto(), BiometricsProtoEnums.ACTION_AUTHENTICATE, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, - error, - 0 /* vendorCode */, mDebugEnabled, latency, - -1 /* sensorId */); + error, + 0 /* vendorCode */, + mUserId); } } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java new file mode 100644 index 000000000000..c5e266f87149 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.log; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.common.OperationContext; + +import java.util.function.Consumer; + +/** + * Cache for system state not directly related to biometric operations that is used for + * logging or optimizations. + */ +public interface BiometricContext { + /** Gets the context source from the system context. */ + static BiometricContext getInstance(@NonNull Context context) { + return BiometricContextProvider.defaultProvider(context); + } + + /** Update the given context with the most recent values and return it. */ + OperationContext updateContext(@NonNull OperationContext operationContext, + boolean isCryptoOperation); + + /** The session id for keyguard entry, if active, or null. */ + @Nullable Integer getKeyguardEntrySessionId(); + + /** The session id for biometric prompt usage, if active, or null. */ + @Nullable Integer getBiometricPromptSessionId(); + + /** If the display is in AOD. */ + boolean isAoD(); + + /** + * Subscribe to context changes. + * + * @param context context that will be modified when changed + * @param consumer callback when the context is modified + */ + void subscribe(@NonNull OperationContext context, @NonNull Consumer<OperationContext> consumer); + + /** Unsubscribe from context changes. */ + void unsubscribe(@NonNull OperationContext context); +} diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java new file mode 100644 index 000000000000..70acaff05e30 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.log; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.StatusBarManager; +import android.content.Context; +import android.hardware.biometrics.IBiometricContextListener; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.common.OperationReason; +import android.hardware.display.AmbientDisplayConfiguration; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.os.UserHandle; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.InstanceId; +import com.android.internal.statusbar.ISessionListener; +import com.android.internal.statusbar.IStatusBarService; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +/** + * A default provider for {@link BiometricContext}. + */ +class BiometricContextProvider implements BiometricContext { + + private static final String TAG = "BiometricContextProvider"; + + private static final int SESSION_TYPES = + StatusBarManager.SESSION_KEYGUARD | StatusBarManager.SESSION_BIOMETRIC_PROMPT; + + private static BiometricContextProvider sInstance; + + static BiometricContextProvider defaultProvider(@NonNull Context context) { + synchronized (BiometricContextProvider.class) { + if (sInstance == null) { + try { + sInstance = new BiometricContextProvider( + new AmbientDisplayConfiguration(context), + IStatusBarService.Stub.asInterface(ServiceManager.getServiceOrThrow( + Context.STATUS_BAR_SERVICE)), null /* handler */); + } catch (ServiceNotFoundException e) { + throw new IllegalStateException("Failed to find required service", e); + } + } + } + return sInstance; + } + + @NonNull + private final Map<OperationContext, Consumer<OperationContext>> mSubscribers = + new ConcurrentHashMap<>(); + + @Nullable + private final Map<Integer, InstanceId> mSession = new ConcurrentHashMap<>(); + + private final AmbientDisplayConfiguration mAmbientDisplayConfiguration; + private boolean mIsDozing = false; + + @VisibleForTesting + BiometricContextProvider(@NonNull AmbientDisplayConfiguration ambientDisplayConfiguration, + @NonNull IStatusBarService service, @Nullable Handler handler) { + mAmbientDisplayConfiguration = ambientDisplayConfiguration; + try { + service.setBiometicContextListener(new IBiometricContextListener.Stub() { + @Override + public void onDozeChanged(boolean isDozing) { + mIsDozing = isDozing; + notifyChanged(); + } + + private void notifyChanged() { + if (handler != null) { + handler.post(() -> notifySubscribers()); + } else { + notifySubscribers(); + } + } + }); + service.registerSessionListener(SESSION_TYPES, new ISessionListener.Stub() { + @Override + public void onSessionStarted(int sessionType, InstanceId instance) { + mSession.put(sessionType, instance); + } + + @Override + public void onSessionEnded(int sessionType, InstanceId instance) { + final InstanceId id = mSession.remove(sessionType); + if (id != null && instance != null && id.getId() != instance.getId()) { + Slog.w(TAG, "session id mismatch"); + } + } + }); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to register biometric context listener", e); + } + } + + @Override + public OperationContext updateContext(@NonNull OperationContext operationContext, + boolean isCryptoOperation) { + operationContext.isAoD = isAoD(); + operationContext.isCrypto = isCryptoOperation; + setFirstSessionId(operationContext); + return operationContext; + } + + private void setFirstSessionId(@NonNull OperationContext operationContext) { + Integer sessionId = getKeyguardEntrySessionId(); + if (sessionId != null) { + operationContext.id = sessionId; + operationContext.reason = OperationReason.KEYGUARD; + return; + } + + sessionId = getBiometricPromptSessionId(); + if (sessionId != null) { + operationContext.id = sessionId; + operationContext.reason = OperationReason.BIOMETRIC_PROMPT; + return; + } + + operationContext.id = 0; + operationContext.reason = OperationReason.UNKNOWN; + } + + @Nullable + @Override + public Integer getKeyguardEntrySessionId() { + final InstanceId id = mSession.get(StatusBarManager.SESSION_KEYGUARD); + return id != null ? id.getId() : null; + } + + @Nullable + @Override + public Integer getBiometricPromptSessionId() { + final InstanceId id = mSession.get(StatusBarManager.SESSION_BIOMETRIC_PROMPT); + return id != null ? id.getId() : null; + } + + @Override + public boolean isAoD() { + return mIsDozing && mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); + } + + @Override + public void subscribe(@NonNull OperationContext context, + @NonNull Consumer<OperationContext> consumer) { + mSubscribers.put(context, consumer); + } + + @Override + public void unsubscribe(@NonNull OperationContext context) { + mSubscribers.remove(context); + } + + private void notifySubscribers() { + mSubscribers.forEach((context, consumer) -> { + context.isAoD = isAoD(); + consumer.accept(context); + }); + } +} diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java index c3471bd1d771..8965227a1bb4 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java @@ -17,6 +17,8 @@ package com.android.server.biometrics.log; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.common.OperationReason; import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; @@ -33,42 +35,49 @@ public class BiometricFrameworkStatsLogger { private BiometricFrameworkStatsLogger() {} + /** Shared instance. */ public static BiometricFrameworkStatsLogger getInstance() { return sInstance; } /** {@see FrameworkStatsLog.BIOMETRIC_ACQUIRED}. */ - public void acquired( + public void acquired(OperationContext operationContext, int statsModality, int statsAction, int statsClient, boolean isDebug, - int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) { + int acquiredInfo, int vendorCode, int targetUserId) { FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ACQUIRED, statsModality, targetUserId, - isCrypto, + operationContext.isCrypto, statsAction, statsClient, acquiredInfo, vendorCode, isDebug, - -1 /* sensorId */); + -1 /* sensorId */, + operationContext.id, + sessionType(operationContext.reason), + operationContext.isAoD); } /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ - public void authenticate( + public void authenticate(OperationContext operationContext, int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, - boolean authenticated, int authState, boolean requireConfirmation, boolean isCrypto, - int targetUserId, boolean isBiometricPrompt, float ambientLightLux) { + int authState, boolean requireConfirmation, + int targetUserId, float ambientLightLux) { FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_AUTHENTICATED, statsModality, targetUserId, - isCrypto, + operationContext.isCrypto, statsClient, requireConfirmation, authState, sanitizeLatency(latency), isDebug, -1 /* sensorId */, - ambientLightLux); + ambientLightLux, + operationContext.id, + sessionType(operationContext.reason), + operationContext.isAoD); } /** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */ @@ -84,20 +93,23 @@ public class BiometricFrameworkStatsLogger { } /** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */ - public void error( + public void error(OperationContext operationContext, int statsModality, int statsAction, int statsClient, boolean isDebug, long latency, - int error, int vendorCode, boolean isCrypto, int targetUserId) { + int error, int vendorCode, int targetUserId) { FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED, statsModality, targetUserId, - isCrypto, + operationContext.isCrypto, statsAction, statsClient, error, vendorCode, isDebug, sanitizeLatency(latency), - -1 /* sensorId */); + -1 /* sensorId */, + operationContext.id, + sessionType(operationContext.reason), + operationContext.isAoD); } /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */ @@ -123,4 +135,14 @@ public class BiometricFrameworkStatsLogger { } return latency; } + + private static int sessionType(@OperationReason byte reason) { + if (reason == OperationReason.BIOMETRIC_PROMPT) { + return BiometricsProtoEnums.SESSION_TYPE_BIOMETRIC_PROMPT; + } + if (reason == OperationReason.KEYGUARD) { + return BiometricsProtoEnums.SESSION_TYPE_KEYGUARD_ENTRY; + } + return BiometricsProtoEnums.SESSION_TYPE_UNKNOWN; + } } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java index d029af38c683..2a8d9f1967eb 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java @@ -25,6 +25,7 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.common.OperationContext; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.util.Slog; @@ -79,6 +80,12 @@ public class BiometricLogger { } }; + /** Get a new logger with all unknown fields (for operations that do not require logs). */ + public static BiometricLogger ofUnknown(@NonNull Context context) { + return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN, + BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + } + /** * @param context system_server context * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants. @@ -103,6 +110,11 @@ public class BiometricLogger { mSensorManager = sensorManager; } + /** Creates a new logger with the action replaced with the new action. */ + public BiometricLogger swapAction(@NonNull Context context, int statsAction) { + return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient); + } + /** Disable logging metrics and only log critical events, such as system health issues. */ public void disableMetrics() { mShouldLogMetrics = false; @@ -133,8 +145,8 @@ public class BiometricLogger { } /** Log an acquisition event. */ - public void logOnAcquired(Context context, - int acquiredInfo, int vendorCode, boolean isCrypto, int targetUserId) { + public void logOnAcquired(Context context, OperationContext operationContext, + int acquiredInfo, int vendorCode, int targetUserId) { if (!mShouldLogMetrics) { return; } @@ -154,7 +166,7 @@ public class BiometricLogger { if (DEBUG) { Slog.v(TAG, "Acquired! Modality: " + mStatsModality + ", User: " + targetUserId - + ", IsCrypto: " + isCrypto + + ", IsCrypto: " + operationContext.isCrypto + ", Action: " + mStatsAction + ", Client: " + mStatsClient + ", AcquiredInfo: " + acquiredInfo @@ -165,14 +177,14 @@ public class BiometricLogger { return; } - mSink.acquired(mStatsModality, mStatsAction, mStatsClient, + mSink.acquired(operationContext, mStatsModality, mStatsAction, mStatsClient, Utils.isDebugEnabled(context, targetUserId), - acquiredInfo, vendorCode, isCrypto, targetUserId); + acquiredInfo, vendorCode, targetUserId); } /** Log an error during an operation. */ - public void logOnError(Context context, - int error, int vendorCode, boolean isCrypto, int targetUserId) { + public void logOnError(Context context, OperationContext operationContext, + int error, int vendorCode, int targetUserId) { if (!mShouldLogMetrics) { return; } @@ -183,7 +195,7 @@ public class BiometricLogger { if (DEBUG) { Slog.v(TAG, "Error! Modality: " + mStatsModality + ", User: " + targetUserId - + ", IsCrypto: " + isCrypto + + ", IsCrypto: " + operationContext.isCrypto + ", Action: " + mStatsAction + ", Client: " + mStatsClient + ", Error: " + error @@ -197,14 +209,14 @@ public class BiometricLogger { return; } - mSink.error(mStatsModality, mStatsAction, mStatsClient, + mSink.error(operationContext, mStatsModality, mStatsAction, mStatsClient, Utils.isDebugEnabled(context, targetUserId), latency, - error, vendorCode, isCrypto, targetUserId); + error, vendorCode, targetUserId); } /** Log authentication attempt. */ - public void logOnAuthenticated(Context context, - boolean authenticated, boolean requireConfirmation, boolean isCrypto, + public void logOnAuthenticated(Context context, OperationContext operationContext, + boolean authenticated, boolean requireConfirmation, int targetUserId, boolean isBiometricPrompt) { if (!mShouldLogMetrics) { return; @@ -230,7 +242,7 @@ public class BiometricLogger { if (DEBUG) { Slog.v(TAG, "Authenticated! Modality: " + mStatsModality + ", User: " + targetUserId - + ", IsCrypto: " + isCrypto + + ", IsCrypto: " + operationContext.isCrypto + ", Client: " + mStatsClient + ", RequireConfirmation: " + requireConfirmation + ", State: " + authState @@ -244,10 +256,9 @@ public class BiometricLogger { return; } - mSink.authenticate(mStatsModality, mStatsAction, mStatsClient, + mSink.authenticate(operationContext, mStatsModality, mStatsAction, mStatsClient, Utils.isDebugEnabled(context, targetUserId), - latency, authenticated, authState, requireConfirmation, isCrypto, - targetUserId, isBiometricPrompt, mLastAmbientLux); + latency, authState, requireConfirmation, targetUserId, mLastAmbientLux); } /** Log enrollment outcome. */ diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index 8b8103e6e2c9..0f0032b72b0a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -29,6 +29,9 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; + import java.util.function.Supplier; /** @@ -57,9 +60,9 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement public AcquisitionClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int cookie, int sensorId, boolean shouldVibrate, - int statsModality, int statsAction, int statsClient) { - super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId, statsModality, - statsAction, statsClient); + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { + super(context, lazyDaemon, token, listener, userId, owner, cookie, sensorId, + logger, biometricContext); mPowerManager = context.getSystemService(PowerManager.class); mShouldVibrate = shouldVibrate; } @@ -107,8 +110,8 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement // that do not handle lockout under the HAL. In these cases, ensure that the framework only // sends errors once per ClientMonitor. if (mShouldSendErrorToClient) { - getLogger().logOnError(getContext(), errorCode, vendorCode, - isCryptoOperation(), getTargetUserId()); + getLogger().logOnError(getContext(), getOperationContext(), + errorCode, vendorCode, getTargetUserId()); try { if (getListener() != null) { mShouldSendErrorToClient = false; @@ -166,8 +169,8 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement protected final void onAcquiredInternal(int acquiredInfo, int vendorCode, boolean shouldSend) { - getLogger().logOnAcquired(getContext(), acquiredInfo, vendorCode, - isCryptoOperation(), getTargetUserId()); + getLogger().logOnAcquired(getContext(), getOperationContext(), + acquiredInfo, vendorCode, getTargetUserId()); if (DEBUG) { Slog.v(TAG, "Acquired: " + acquiredInfo + " " + vendorCode + ", shouldSend: " + shouldSend); diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index b715faf3ca8f..54b79e1f8e4a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -29,7 +29,6 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricOverlayConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; @@ -39,6 +38,8 @@ import android.util.Slog; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import java.util.ArrayList; import java.util.List; @@ -93,13 +94,13 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> public AuthenticationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, boolean restricted, @NonNull String owner, - int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric, - int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener, + int cookie, boolean requireConfirmation, int sensorId, + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, + boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener, @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication, boolean shouldVibrate, boolean isKeyguardBypassEnabled) { super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId, - shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE, - statsClient); + shouldVibrate, biometricLogger, biometricContext); mIsStrongBiometric = isStrongBiometric; mOperationId = operationId; mRequireConfirmation = requireConfirmation; @@ -165,8 +166,8 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> @Override public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> hardwareAuthToken) { - getLogger().logOnAuthenticated(getContext(), authenticated, mRequireConfirmation, - isCryptoOperation(), getTargetUserId(), isBiometricPrompt()); + getLogger().logOnAuthenticated(getContext(), getOperationContext(), + authenticated, mRequireConfirmation, getTargetUserId(), isBiometricPrompt()); final ClientMonitorCallbackConverter listener = getListener(); diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index e1f7e2ab5461..1b2e606117e7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -21,12 +21,12 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import java.util.NoSuchElementException; @@ -50,6 +50,7 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient { @NonNull private final String mOwner; private final int mSensorId; // sensorId as configured by the framework @NonNull private final BiometricLogger mLogger; + @NonNull private final BiometricContext mBiometricContext; @Nullable private IBinder mToken; private long mRequestId; @@ -82,22 +83,13 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient { * @param owner name of the client that owns this * @param cookie BiometricPrompt authentication cookie (to be moved into a subclass soon) * @param sensorId ID of the sensor that the operation should be requested of - * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants - * @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants - * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants + * @param logger framework stats logger + * @param biometricContext system context metadata */ public BaseClientMonitor(@NonNull Context context, @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction, - int statsClient) { - this(context, token, listener, userId, owner, cookie, sensorId, - new BiometricLogger(context, statsModality, statsAction, statsClient)); - } - - @VisibleForTesting - BaseClientMonitor(@NonNull Context context, - @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int cookie, int sensorId, @NonNull BiometricLogger logger) { + @NonNull String owner, int cookie, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { mSequentialId = sCount++; mContext = context; mToken = token; @@ -108,6 +100,7 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient { mCookie = cookie; mSensorId = sensorId; mLogger = logger; + mBiometricContext = biometricContext; try { if (token != null) { @@ -207,20 +200,29 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient { return false; } + /** System context that may change during operations. */ + @NonNull + protected BiometricContext getBiometricContext() { + return mBiometricContext; + } + /** Logger for this client */ @NonNull public BiometricLogger getLogger() { return mLogger; } + @NonNull public final Context getContext() { return mContext; } + @NonNull public final String getOwnerString() { return mOwner; } + @Nullable public final ClientMonitorCallbackConverter getListener() { return mListener; } @@ -229,6 +231,7 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient { return mTargetUserId; } + @Nullable public final IBinder getToken() { return mToken; } diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java index 74f4931cf2aa..483ce75eae98 100644 --- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java @@ -20,13 +20,14 @@ import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricOverlayConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.fingerprint.FingerprintManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import java.util.Arrays; import java.util.function.Supplier; @@ -53,10 +54,10 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En public EnrollClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils, - int timeoutSec, int statsModality, int sensorId, boolean shouldVibrate) { + int timeoutSec, int sensorId, boolean shouldVibrate, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, - shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_ENROLL, - BiometricsProtoEnums.CLIENT_UNKNOWN); + shouldVibrate, logger, biometricContext); mBiometricUtils = utils; mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length); mTimeoutSec = timeoutSec; diff --git a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java index 9689418b1f1a..2adf0cb3bab4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/GenerateChallengeClient.java @@ -18,12 +18,13 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import java.util.function.Supplier; @@ -33,10 +34,10 @@ public abstract class GenerateChallengeClient<T> extends HalClientMonitor<T> { public GenerateChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, - int userId, @NonNull String owner, int sensorId) { + int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, - BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN); + biometricLogger, biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java index 66a1c6e876ab..a6e89115400d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/HalClientMonitor.java @@ -19,9 +19,12 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.common.OperationContext; import android.os.IBinder; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; + import java.util.function.Supplier; /** @@ -33,6 +36,9 @@ public abstract class HalClientMonitor<T> extends BaseClientMonitor { @NonNull protected final Supplier<T> mLazyDaemon; + @NonNull + private final OperationContext mOperationContext = new OperationContext(); + /** * @param context system_server context * @param lazyDaemon pointer for lazy retrieval of the HAL @@ -42,16 +48,15 @@ public abstract class HalClientMonitor<T> extends BaseClientMonitor { * @param owner name of the client that owns this * @param cookie BiometricPrompt authentication cookie (to be moved into a subclass soon) * @param sensorId ID of the sensor that the operation should be requested of - * @param statsModality One of {@link BiometricsProtoEnums} MODALITY_* constants - * @param statsAction One of {@link BiometricsProtoEnums} ACTION_* constants - * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants + * @param biometricLogger framework stats logger + * @param biometricContext system context metadata */ public HalClientMonitor(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @Nullable IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int cookie, int sensorId, int statsModality, int statsAction, - int statsClient) { - super(context, token, listener, userId, owner, cookie, sensorId, statsModality, - statsAction, statsClient); + @NonNull String owner, int cookie, int sensorId, + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) { + super(context, token, listener, userId, owner, cookie, sensorId, + biometricLogger, biometricContext); mLazyDaemon = lazyDaemon; } @@ -71,4 +76,29 @@ public abstract class HalClientMonitor<T> extends BaseClientMonitor { * {@link #start(ClientMonitorCallback)}. */ public abstract void unableToStart(); + + @Override + public void destroy() { + super.destroy(); + + // subclasses should do this earlier in most cases, but ensure it happens now + unsubscribeBiometricContext(); + } + + protected OperationContext getOperationContext() { + return getBiometricContext().updateContext(mOperationContext, isCryptoOperation()); + } + + protected ClientMonitorCallback getBiometricContextUnsubscriber() { + return new ClientMonitorCallback() { + @Override + public void onClientFinished(@NonNull BaseClientMonitor monitor, boolean success) { + unsubscribeBiometricContext(); + } + }; + } + + protected void unsubscribeBiometricContext() { + getBiometricContext().unsubscribe(mOperationContext); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java index 0e6d11ec5182..57ea812dbb3a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java @@ -19,11 +19,12 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import java.util.ArrayList; import java.util.List; @@ -101,19 +102,22 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide protected abstract InternalEnumerateClient<T> getEnumerateClient(Context context, Supplier<T> lazyDaemon, IBinder token, int userId, String owner, - List<S> enrolledList, BiometricUtils<S> utils, int sensorId); + List<S> enrolledList, BiometricUtils<S> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext); protected abstract RemovalClient<S, T> getRemovalClient(Context context, Supplier<T> lazyDaemon, IBinder token, int biometricId, int userId, String owner, - BiometricUtils<S> utils, int sensorId, Map<Integer, Long> authenticatorIds); + BiometricUtils<S> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + Map<Integer, Long> authenticatorIds); protected InternalCleanupClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, - int userId, @NonNull String owner, int sensorId, int statsModality, + int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull List<S> enrolledList, @NonNull BiometricUtils<S> utils, @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, null /* token */, null /* ClientMonitorCallbackConverter */, - userId, owner, 0 /* cookie */, sensorId, statsModality, - BiometricsProtoEnums.ACTION_ENUMERATE, BiometricsProtoEnums.CLIENT_UNKNOWN); + userId, owner, 0 /* cookie */, sensorId, logger, biometricContext); mBiometricUtils = utils; mAuthenticatorIds = authenticatorIds; mEnrolledList = enrolledList; @@ -127,7 +131,8 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide mUnknownHALTemplates.remove(template); mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(), template.mIdentifier.getBiometricId(), template.mUserId, - getContext().getPackageName(), mBiometricUtils, getSensorId(), mAuthenticatorIds); + getContext().getPackageName(), mBiometricUtils, getSensorId(), + getLogger(), getBiometricContext(), mAuthenticatorIds); getLogger().logUnknownEnrollmentInHal(); @@ -145,7 +150,8 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide // Start enumeration. Removal will start if necessary, when enumeration is completed. mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(), - getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId()); + getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId(), getLogger(), + getBiometricContext()); Slog.d(TAG, "Starting enumerate: " + mCurrentTask); mCurrentTask.start(mEnumerateCallback); diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java index 5f97f3711a60..7f8f38f3e9d2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java @@ -19,11 +19,12 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import java.util.ArrayList; import java.util.List; @@ -47,12 +48,14 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, @NonNull List<? extends BiometricAuthenticator.Identifier> enrolledList, - @NonNull BiometricUtils utils, int sensorId, int statsModality) { + @NonNull BiometricUtils utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { // Internal enumerate does not need to send results to anyone. Cleanup (enumerate + remove) // is all done internally. super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner, - 0 /* cookie */, sensorId, statsModality, BiometricsProtoEnums.ACTION_ENUMERATE, - BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, logger, biometricContext); + //, BiometricsProtoEnums.ACTION_ENUMERATE, + // BiometricsProtoEnums.CLIENT_UNKNOWN); mEnrolledList = enrolledList; mUtils = utils; } diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java index 697d77cfbee6..d5aa5e2c9db6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationClient.java @@ -19,12 +19,13 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IInvalidationCallback; import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import java.util.Map; import java.util.function.Supplier; @@ -42,12 +43,13 @@ public abstract class InvalidationClient<S extends BiometricAuthenticator.Identi @NonNull private final IInvalidationCallback mInvalidationCallback; public InvalidationClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, - int userId, int sensorId, @NonNull Map<Integer, Long> authenticatorIds, + int userId, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + @NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, context.getOpPackageName(), 0 /* cookie */, sensorId, - BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN); + logger, biometricContext); mAuthenticatorIds = authenticatorIds; mInvalidationCallback = callback; } diff --git a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java index b2661a28012d..1097bb7da684 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InvalidationRequesterClient.java @@ -20,10 +20,11 @@ import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IInvalidationCallback; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; /** * ClientMonitor subclass responsible for coordination of authenticatorId invalidation of other @@ -74,11 +75,10 @@ public class InvalidationRequesterClient<S extends BiometricAuthenticator.Identi }; public InvalidationRequesterClient(@NonNull Context context, int userId, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull BiometricUtils<S> utils) { super(context, null /* token */, null /* listener */, userId, - context.getOpPackageName(), 0 /* cookie */, sensorId, - BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN); + context.getOpPackageName(), 0 /* cookie */, sensorId, logger, biometricContext); mBiometricManager = context.getSystemService(BiometricManager.class); mUtils = utils; } diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java index a0cef94fcf48..07ce841a7cac 100644 --- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java @@ -19,12 +19,13 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import java.util.Map; import java.util.function.Supplier; @@ -44,10 +45,12 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier, public RemovalClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, @NonNull BiometricUtils<S> utils, int sensorId, - @NonNull Map<Integer, Long> authenticatorIds, int statsModality) { + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, - statsModality, BiometricsProtoEnums.ACTION_REMOVE, - BiometricsProtoEnums.CLIENT_UNKNOWN); + logger, biometricContext); + //, BiometricsProtoEnums.ACTION_REMOVE, + // BiometricsProtoEnums.CLIENT_UNKNOWN); mBiometricUtils = utils; mAuthenticatorIds = authenticatorIds; mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty(); diff --git a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java index 7d8386337ece..88f4da261d62 100644 --- a/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/RevokeChallengeClient.java @@ -18,20 +18,21 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import java.util.function.Supplier; public abstract class RevokeChallengeClient<T> extends HalClientMonitor<T> { public RevokeChallengeClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, - @NonNull IBinder token, int userId, @NonNull String owner, int sensorId) { + @NonNull IBinder token, int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) { super(context, lazyDaemon, token, null /* listener */, userId, owner, - 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, biometricLogger, biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java index 1bc3248cd0e7..21c9f64eea5b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java @@ -19,11 +19,12 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import java.util.function.Supplier; @@ -47,10 +48,10 @@ public abstract class StartUserClient<T, U> extends HalClientMonitor<T> { public StartUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @Nullable IBinder token, int userId, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull UserStartedCallback<U> callback) { super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(), - 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, logger, biometricContext); mUserStartedCallback = callback; } diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java index 3eafbb8ea9d7..e8654dc059a4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java @@ -19,11 +19,12 @@ package com.android.server.biometrics.sensors; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.IBinder; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import java.util.function.Supplier; @@ -47,10 +48,10 @@ public abstract class StopUserClient<T> extends HalClientMonitor<T> { public StopUserClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @Nullable IBinder token, int userId, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull UserStoppedCallback callback) { super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(), - 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, logger, biometricContext); mUserStoppedCallback = callback; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 039b08e805c1..2e820574b435 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -57,6 +57,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -645,7 +646,7 @@ public class FaceService extends SystemService { try { final SensorProps[] props = face.getSensorProps(); final FaceProvider provider = new FaceProvider(getContext(), props, instance, - mLockoutResetDispatcher); + mLockoutResetDispatcher, BiometricContext.getInstance(getContext())); mServiceProviders.add(provider); } catch (RemoteException e) { Slog.e(TAG, "Remote exception in getSensorProps: " + fqName); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java index 006667ac659f..29eee6b5bb06 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java @@ -16,11 +16,11 @@ package com.android.server.biometrics.sensors.face.aidl; +import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback; + import android.annotation.NonNull; import android.hardware.biometrics.face.ISession; -import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback; - /** * A holder for an AIDL {@link ISession} with additional metadata about the current user * and the backend. diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index c4e050215134..7765ab3b8e53 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -25,10 +25,7 @@ import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; -import android.hardware.biometrics.common.OperationContext; -import android.hardware.biometrics.common.OperationReason; import android.hardware.biometrics.face.IFace; import android.hardware.face.FaceAuthenticationFrame; import android.hardware.face.FaceManager; @@ -37,7 +34,10 @@ import android.os.RemoteException; import android.util.Slog; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -76,19 +76,36 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId, - boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + boolean isStrongBiometric, @NonNull UsageStats usageStats, @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) { + this(context, lazyDaemon, token, requestId, listener, targetUserId, operationId, + restricted, owner, cookie, requireConfirmation, sensorId, logger, biometricContext, + isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication, + isKeyguardBypassEnabled, context.getSystemService(SensorPrivacyManager.class)); + } + + @VisibleForTesting + FaceAuthenticationClient(@NonNull Context context, + @NonNull Supplier<AidlSession> lazyDaemon, + @NonNull IBinder token, long requestId, + @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, + boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + boolean isStrongBiometric, @NonNull UsageStats usageStats, + @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, + boolean isKeyguardBypassEnabled, SensorPrivacyManager sensorPrivacyManager) { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, - owner, cookie, requireConfirmation, sensorId, isStrongBiometric, - BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */, - lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */, + owner, cookie, requireConfirmation, sensorId, logger, biometricContext, + isStrongBiometric, null /* taskStackListener */, lockoutCache, + allowBackgroundAuthentication, true /* shouldVibrate */, isKeyguardBypassEnabled); setRequestId(requestId); mUsageStats = usageStats; mLockoutCache = lockoutCache; mNotificationManager = context.getSystemService(NotificationManager.class); - mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); + mSensorPrivacyManager = sensorPrivacyManager; final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -138,13 +155,8 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> final AidlSession session = getFreshDaemon(); if (session.hasContextMethods()) { - final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD - context.id = 0; - context.reason = OperationReason.UNKNOWN; - context.isAoD = false; - context.isCrypto = isCryptoOperation(); - return session.getSession().authenticateWithContext(mOperationId, context); + return session.getSession().authenticateWithContext( + mOperationId, getOperationContext()); } else { return session.getSession().authenticate(mOperationId); } @@ -263,8 +275,8 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT; - getLogger().logOnError(getContext(), error, 0 /* vendorCode */, - isCryptoOperation(), getTargetUserId()); + getLogger().logOnError(getContext(), getOperationContext(), + error, 0 /* vendorCode */, getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); @@ -279,8 +291,8 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT); // Lockout metrics are logged as an error code. final int error = BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT; - getLogger().logOnError(getContext(), error, 0 /* vendorCode */, - isCryptoOperation(), getTargetUserId()); + getLogger().logOnError(getContext(), getOperationContext(), + error, 0 /* vendorCode */, getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java index 3f3db4342a7a..efedcf815228 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java @@ -21,15 +21,15 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; -import android.hardware.biometrics.common.OperationContext; -import android.hardware.biometrics.common.OperationReason; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -52,13 +52,26 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int sensorId, boolean isStrongBiometric, int statsClient) { + @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + boolean isStrongBiometric) { + this(context, lazyDaemon, token, requestId, listener, userId, owner, sensorId, + logger, biometricContext, isStrongBiometric, + context.getSystemService(SensorPrivacyManager.class)); + } + + @VisibleForTesting + FaceDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, + @NonNull IBinder token, long requestId, + @NonNull ClientMonitorCallbackConverter listener, int userId, + @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + boolean isStrongBiometric, SensorPrivacyManager sensorPrivacyManager) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, - true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FACE, - BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); + true /* shouldVibrate */, logger, biometricContext); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; - mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); + mSensorPrivacyManager = sensorPrivacyManager; } @Override @@ -101,13 +114,7 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements final AidlSession session = getFreshDaemon(); if (session.hasContextMethods()) { - final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD - context.id = 0; - context.reason = OperationReason.UNKNOWN; - context.isAoD = false; - context.isCrypto = isCryptoOperation(); - return session.getSession().detectInteractionWithContext(context); + return session.getSession().detectInteractionWithContext(getOperationContext()); } else { return session.getSession().detectInteraction(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index 8dc53b6346a4..da7853654ebf 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -20,10 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricFaceConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; -import android.hardware.biometrics.common.OperationContext; -import android.hardware.biometrics.common.OperationReason; import android.hardware.biometrics.face.EnrollmentType; import android.hardware.biometrics.face.Feature; import android.hardware.biometrics.face.IFace; @@ -40,6 +37,8 @@ import android.view.Surface; import com.android.internal.R; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; @@ -89,11 +88,11 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, - @Nullable Surface previewSurface, int sensorId, int maxTemplatesPerUser, - boolean debugConsent) { + @Nullable Surface previewSurface, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + int maxTemplatesPerUser, boolean debugConsent) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils, - timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId, - false /* shouldVibrate */); + timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext); setRequestId(requestId); mEnrollIgnoreList = getContext().getResources() .getIntArray(R.array.config_face_acquire_enroll_ignorelist); @@ -199,14 +198,8 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken); if (session.hasContextMethods()) { - final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD - context.id = 0; - context.reason = OperationReason.UNKNOWN; - context.isAoD = false; - context.isCrypto = isCryptoOperation(); return session.getSession().enrollWithContext( - hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, context); + hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, getOperationContext()); } else { return session.getSession().enroll(hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java index bdad268b9422..165c3a241043 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.GenerateChallengeClient; @@ -37,8 +39,10 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSes FaceGenerateChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, - int sensorId) { - super(context, lazyDaemon, token, listener, userId, owner, sensorId); + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext) { + super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger, + biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java index 2f3187bb4fa0..1f4f6127dafd 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetAuthenticatorIdClient.java @@ -18,11 +18,12 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -38,10 +39,10 @@ class FaceGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> { FaceGetAuthenticatorIdClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String opPackageName, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, opPackageName, - 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FACE, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, logger, biometricContext); mAuthenticatorIds = authenticatorIds; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java index 79479bebc759..ef3b345402bf 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricFaceConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.IFace; import android.os.IBinder; import android.os.RemoteException; @@ -28,6 +27,8 @@ import android.provider.Settings; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ErrorConsumer; @@ -48,10 +49,10 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int sensorId) { + @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, - BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN); + logger, biometricContext); mUserId = userId; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java index a2b0339b282f..54f2033b363a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java @@ -18,11 +18,12 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.IFace; import android.hardware.face.Face; import android.os.IBinder; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.InternalCleanupClient; import com.android.server.biometrics.sensors.InternalEnumerateClient; @@ -39,29 +40,32 @@ class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> FaceInternalCleanupClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, - int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils, - @NonNull Map<Integer, Long> authenticatorIds) { - super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE, + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList, + @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) { + super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, enrolledList, utils, authenticatorIds); } @Override protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context, Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner, - List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) { + List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner, - enrolledList, utils, sensorId); + enrolledList, utils, sensorId, logger, biometricContext); } @Override protected RemovalClient<Face, AidlSession> getRemovalClient(Context context, Supplier<AidlSession> lazyDaemon, IBinder token, int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) { // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove) // is all done internally. return new FaceRemovalClient(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner, - utils, sensorId, authenticatorIds); + utils, sensorId, logger, biometricContext, authenticatorIds); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java index 88c9d3bd1035..d85455e0e76b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java @@ -18,13 +18,14 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.IFace; import android.hardware.face.Face; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.InternalEnumerateClient; @@ -40,9 +41,10 @@ class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> { FaceInternalEnumerateClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, @NonNull List<Face> enrolledList, - @NonNull BiometricUtils<Face> utils, int sensorId) { + @NonNull BiometricUtils<Face> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId, - BiometricsProtoEnums.MODALITY_FACE); + logger, biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java index 04ea2cfc6eff..39d8de07f652 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInvalidationClient.java @@ -23,6 +23,8 @@ import android.hardware.face.Face; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.InvalidationClient; import java.util.Map; @@ -33,8 +35,10 @@ public class FaceInvalidationClient extends InvalidationClient<Face, AidlSession public FaceInvalidationClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) { - super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback); + super(context, lazyDaemon, userId, sensorId, logger, biometricContext, + authenticatorIds, callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 9d7a5529f473..4e03ee9a618c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -23,6 +23,7 @@ import android.app.ActivityTaskManager; import android.app.TaskStackListener; import android.content.Context; import android.content.pm.UserInfo; +import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; @@ -47,6 +48,8 @@ import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -87,7 +90,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { @NonNull private final BiometricTaskStackListener mTaskStackListener; // for requests that do not use biometric prompt @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0); - + @NonNull private final BiometricContext mBiometricContext; @Nullable private IFace mDaemon; private final class BiometricTaskStackListener extends TaskStackListener { @@ -125,7 +128,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { public FaceProvider(@NonNull Context context, @NonNull SensorProps[] props, @NonNull String halInstanceName, - @NonNull LockoutResetDispatcher lockoutResetDispatcher) { + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull BiometricContext biometricContext) { mContext = context; mHalInstanceName = halInstanceName; mSensors = new SparseArray<>(); @@ -134,6 +138,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mLockoutResetDispatcher = lockoutResetDispatcher; mActivityTaskManager = ActivityTaskManager.getInstance(); mTaskStackListener = new BiometricTaskStackListener(); + mBiometricContext = biometricContext; for (SensorProps prop : props) { final int sensorId = prop.commonProps.sensorId; @@ -153,7 +158,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { prop.supportsDetectInteraction, prop.halControlsPreview, false /* resetLockoutRequiresChallenge */); final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler, - internalProp, lockoutResetDispatcher); + internalProp, lockoutResetDispatcher, mBiometricContext); mSensors.put(sensorId, sensor); Slog.d(getTag(), "Added: " + internalProp); @@ -237,6 +242,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient( mContext, mSensors.get(sensorId).getLazySession(), userId, mContext.getOpPackageName(), sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client); @@ -247,6 +255,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { final InvalidationRequesterClient<Face> client = new InvalidationRequesterClient<>(mContext, userId, sensorId, + BiometricLogger.ofUnknown(mContext), + mBiometricContext, FaceUtils.getInstance(sensorId)); scheduleForSensor(sensorId, client); }); @@ -285,6 +295,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { final FaceInvalidationClient client = new FaceInvalidationClient(mContext, mSensors.get(sensorId).getLazySession(), userId, sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mSensors.get(sensorId).getAuthenticatorIds(), callback); scheduleForSensor(sensorId, client); }); @@ -311,7 +324,10 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, mSensors.get(sensorId).getLazySession(), token, - new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId); + new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext); scheduleForSensor(sensorId, client); }); } @@ -322,7 +338,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, mSensors.get(sensorId).getLazySession(), token, userId, opPackageName, sensorId, - challenge); + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, challenge); scheduleForSensor(sensorId, client); }); } @@ -340,8 +358,10 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures, - ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser, - debugConsent); + ENROLL_TIMEOUT_SEC, previewSurface, sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, maxTemplatesPerUser, debugConsent); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, @@ -372,8 +392,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); final FaceDetectClient client = new FaceDetectClient(mContext, mSensors.get(sensorId).getLazySession(), - token, id, callback, userId, opPackageName, - sensorId, isStrongBiometric, statsClient); + token, id, callback, userId, opPackageName, sensorId, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), + mBiometricContext, isStrongBiometric); scheduleForSensor(sensorId, client); }); @@ -396,7 +417,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final FaceAuthenticationClient client = new FaceAuthenticationClient( mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback, userId, operationId, restricted, opPackageName, cookie, - false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient, + false /* requireConfirmation */, sensorId, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), + mBiometricContext, isStrongBiometric, mUsageStats, mSensors.get(sensorId).getLockoutCache(), allowBackgroundAuthentication, isKeyguardBypassEnabled); scheduleForSensor(sensorId, client); @@ -450,6 +473,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), faceIds, userId, opPackageName, FaceUtils.getInstance(sensorId), sensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client); }); @@ -460,7 +486,10 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { final FaceResetLockoutClient client = new FaceResetLockoutClient( mContext, mSensors.get(sensorId).getLazySession(), userId, - mContext.getOpPackageName(), sensorId, hardwareAuthToken, + mContext.getOpPackageName(), sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, hardwareAuthToken, mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); scheduleForSensor(sensorId, client); @@ -481,7 +510,9 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), userId, - mContext.getOpPackageName(), sensorId, feature, enabled, hardwareAuthToken); + mContext.getOpPackageName(), sensorId, + BiometricLogger.ofUnknown(mContext), mBiometricContext, + feature, enabled, hardwareAuthToken); scheduleForSensor(sensorId, client); }); } @@ -498,7 +529,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { } final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId, - mContext.getOpPackageName(), sensorId); + mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext), + mBiometricContext); scheduleForSensor(sensorId, client); }); } @@ -518,13 +550,21 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, mSensors.get(sensorId).getLazySession(), userId, - mContext.getOpPackageName(), sensorId, enrolledList, + mContext.getOpPackageName(), sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, enrolledList, FaceUtils.getInstance(sensorId), mSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client, callback); }); } + private BiometricLogger createLogger(int statsAction, int statsClient) { + return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE, + statsAction, statsClient); + } + @Override public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto, boolean clearSchedulerBuffer) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java index 130a05a861d9..0512017394af 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java @@ -18,13 +18,14 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.IFace; import android.hardware.face.Face; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.RemovalClient; @@ -44,9 +45,10 @@ class FaceRemovalClient extends RemovalClient<Face, AidlSession> { @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int[] biometricIds, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, - authenticatorIds, BiometricsProtoEnums.MODALITY_FACE); + logger, biometricContext, authenticatorIds); mBiometricIds = biometricIds; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index 67bf3f5b2e4f..de0a36a32b66 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -18,7 +18,6 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.IFace; import android.hardware.keymaster.HardwareAuthToken; import android.os.RemoteException; @@ -26,6 +25,8 @@ import android.util.Slog; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -50,11 +51,11 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem FaceResetLockoutClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, - 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, logger, biometricContext); mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken); mLockoutCache = lockoutTracker; mLockoutResetDispatcher = lockoutResetDispatcher; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java index acd2e0589ccc..8838345de4d6 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.RevokeChallengeClient; import java.util.function.Supplier; @@ -38,8 +40,10 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<AidlSession FaceRevokeChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, - int userId, @NonNull String owner, int sensorId, long challenge) { - super(context, lazyDaemon, token, userId, owner, sensorId); + int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + long challenge) { + super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext); mChallenge = challenge; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java index 9d535a26e12d..6c143872ff8c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java @@ -18,7 +18,6 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.IFace; import android.hardware.keymaster.HardwareAuthToken; import android.os.IBinder; @@ -27,6 +26,8 @@ import android.util.Slog; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.ErrorConsumer; @@ -47,11 +48,11 @@ public class FaceSetFeatureClient extends HalClientMonitor<AidlSession> implemen FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int sensorId, int feature, boolean enabled, - byte[] hardwareAuthToken) { + @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + int feature, boolean enabled, byte[] hardwareAuthToken) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, - BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN); + logger, biometricContext); mFeature = feature; mEnabled = enabled; mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java index f5a98ff5881b..61e7ab781e66 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java @@ -27,6 +27,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.StartUserClient; @@ -40,9 +42,10 @@ public class FaceStartUserClient extends StartUserClient<IFace, ISession> { public FaceStartUserClient(@NonNull Context context, @NonNull Supplier<IFace> lazyDaemon, @Nullable IBinder token, int userId, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull ISessionCallback sessionCallback, @NonNull UserStartedCallback<ISession> callback) { - super(context, lazyDaemon, token, userId, sensorId, callback); + super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback); mSessionCallback = sessionCallback; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java index 48b4856fa4b6..0110ae991ae4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.StopUserClient; @@ -33,8 +35,9 @@ public class FaceStopUserClient extends StopUserClient<AidlSession> { public FaceStopUserClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @Nullable IBinder token, int userId, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull UserStoppedCallback callback) { - super(context, lazyDaemon, token, userId, sensorId, callback); + super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 33e6fa4ebf93..b69c7600e75a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -42,12 +42,15 @@ import android.os.UserManager; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; @@ -88,7 +91,8 @@ public class Sensor { @NonNull private final Supplier<AidlSession> mLazySession; @Nullable private AidlSession mCurrentSession; - static class HalSessionCallback extends ISessionCallback.Stub { + @VisibleForTesting + public static class HalSessionCallback extends ISessionCallback.Stub { /** * Interface to sends results to the HalSessionCallback's owner. */ @@ -472,7 +476,8 @@ public class Sensor { Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, - @NonNull LockoutResetDispatcher lockoutResetDispatcher) { + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull BiometricContext biometricContext) { mTag = tag; mProvider = provider; mContext = context; @@ -487,7 +492,9 @@ public class Sensor { @Override public StopUserClient<?> getStopUserClient(int userId) { return new FaceStopUserClient(mContext, mLazySession, mToken, userId, - mSensorProperties.sensorId, () -> mCurrentSession = null); + mSensorProperties.sensorId, + BiometricLogger.ofUnknown(mContext), biometricContext, + () -> mCurrentSession = null); } @NonNull @@ -523,6 +530,7 @@ public class Sensor { return new FaceStartUserClient(mContext, provider::getHalInstance, mToken, newUserId, mSensorProperties.sensorId, + BiometricLogger.ofUnknown(mContext), biometricContext, resultController, userStartedCallback); } }); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 586abe2d6298..73c759f7738c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -55,6 +55,8 @@ import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; @@ -117,6 +119,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @NonNull private final Map<Integer, Long> mAuthenticatorIds; @Nullable private IBiometricsFace mDaemon; @NonNull private final HalResultController mHalResultController; + @NonNull private final BiometricContext mBiometricContext; // for requests that do not use biometric prompt @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0); private int mCurrentUserId = UserHandle.USER_NULL; @@ -153,6 +156,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @NonNull private final LockoutHalImpl mLockoutTracker; @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher; + HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler, @NonNull BiometricScheduler scheduler, @NonNull LockoutHalImpl lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher) { @@ -335,12 +339,14 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { @NonNull FaceSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher, @NonNull Handler handler, - @NonNull BiometricScheduler scheduler) { + @NonNull BiometricScheduler scheduler, + @NonNull BiometricContext biometricContext) { mSensorProperties = sensorProps; mContext = context; mSensorId = sensorProps.sensorId; mScheduler = scheduler; mHandler = handler; + mBiometricContext = biometricContext; mUsageStats = new UsageStats(context); mAuthenticatorIds = new HashMap<>(); mLazyDaemon = Face10.this::getDaemon; @@ -365,7 +371,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final Handler handler = new Handler(Looper.getMainLooper()); return new Face10(context, sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE, - null /* gestureAvailabilityTracker */)); + null /* gestureAvailabilityTracker */), + BiometricContext.getInstance(context)); } @Override @@ -533,7 +540,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - opPackageName, mSensorId, sSystemClock.millis()); + opPackageName, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, sSystemClock.millis()); mGeneratedChallengeCache = client; mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override @@ -562,7 +572,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { mGeneratedChallengeCache = null; final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, - mLazyDaemon, token, userId, opPackageName, mSensorId); + mLazyDaemon, token, userId, opPackageName, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, @@ -590,7 +603,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, - ENROLL_TIMEOUT_SEC, previewSurface, mSensorId); + ENROLL_TIMEOUT_SEC, previewSurface, mSensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override @@ -637,8 +653,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, mLazyDaemon, token, requestId, receiver, userId, operationId, restricted, opPackageName, cookie, false /* requireConfirmation */, mSensorId, - isStrongBiometric, statsClient, mLockoutTracker, mUsageStats, - allowBackgroundAuthentication, isKeyguardBypassEnabled); + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), + mBiometricContext, isStrongBiometric, mLockoutTracker, + mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled); mScheduler.scheduleClientMonitor(client); }); } @@ -670,7 +687,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName, - FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds); + FaceUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mAuthenticatorIds); mScheduler.scheduleClientMonitor(client); }); } @@ -685,7 +705,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId, opPackageName, - FaceUtils.getLegacyInstance(mSensorId), mSensorId, mAuthenticatorIds); + FaceUtils.getLegacyInstance(mSensorId), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mAuthenticatorIds); mScheduler.scheduleClientMonitor(client); }); } @@ -702,7 +725,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext, mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, - hardwareAuthToken); + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, hardwareAuthToken); mScheduler.scheduleClientMonitor(client); }); } @@ -723,7 +748,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final int faceId = faces.get(0).getBiometricId(); final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, - opPackageName, mSensorId, feature, enabled, hardwareAuthToken, faceId); + opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext), + mBiometricContext, + feature, enabled, hardwareAuthToken, faceId); mScheduler.scheduleClientMonitor(client); }); } @@ -742,7 +769,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final int faceId = faces.get(0).getBiometricId(); final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon, - token, listener, userId, opPackageName, mSensorId, feature, faceId); + token, listener, userId, opPackageName, mSensorId, + BiometricLogger.ofUnknown(mContext), mBiometricContext, + feature, faceId); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished( @@ -767,7 +796,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final List<Face> enrolledList = getEnrolledFaces(mSensorId, userId); final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, - mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, enrolledList, + mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, enrolledList, FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, callback); }); @@ -890,7 +922,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { final boolean hasEnrolled = !getEnrolledFaces(mSensorId, targetUserId).isEmpty(); final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId, - hasEnrolled, mAuthenticatorIds); + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, hasEnrolled, mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, @@ -904,6 +938,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider { }); } + private BiometricLogger createLogger(int statsAction, int statsClient) { + return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE, + statsAction, statsClient); + } + /** * Sends a debug message to the HAL with the provided FileDescriptor and arguments. */ diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java index 9038435c1021..8d76e9f031f7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java @@ -23,7 +23,6 @@ import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFaceConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.face.FaceManager; import android.os.IBinder; @@ -32,6 +31,8 @@ import android.util.Slog; import com.android.internal.R; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -66,12 +67,13 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId, - boolean isStrongBiometric, int statsClient, @NonNull LockoutTracker lockoutTracker, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker, @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, - owner, cookie, requireConfirmation, sensorId, isStrongBiometric, - BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */, + owner, cookie, requireConfirmation, sensorId, logger, biometricContext, + isStrongBiometric, null /* taskStackListener */, lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */, isKeyguardBypassEnabled); setRequestId(requestId); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java index 92f7253779ed..226e458ad07b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java @@ -20,7 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricFaceConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.Status; import android.hardware.face.Face; @@ -32,6 +31,8 @@ import android.view.Surface; import com.android.internal.R; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -58,10 +59,10 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> { @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId, @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, - @Nullable Surface previewSurface, int sensorId) { + @Nullable Surface previewSurface, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, - timeoutSec, BiometricsProtoEnums.MODALITY_FACE, sensorId, - false /* shouldVibrate */); + timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext); setRequestId(requestId); mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length); mEnrollIgnoreList = getContext().getResources() diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java index b66ad608b4ca..97838a70cbd1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java @@ -25,6 +25,8 @@ import android.os.RemoteException; import android.util.Slog; import com.android.internal.util.Preconditions; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.GenerateChallengeClient; @@ -51,8 +53,10 @@ public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiomet FaceGenerateChallengeClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, - int sensorId, long now) { - super(context, lazyDaemon, token, listener, userId, owner, sensorId); + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, long now) { + super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger, + biometricContext); mCreatedAt = now; mWaiting = new ArrayList<>(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java index 1b387bf7879a..981253699322 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java @@ -19,7 +19,6 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.OptionalBool; import android.hardware.biometrics.face.V1_0.Status; @@ -28,6 +27,8 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -48,13 +49,13 @@ public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> { FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int sensorId, int feature, int faceId) { + @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + int feature, int faceId) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, - BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN); + logger, biometricContext); mFeature = feature; mFaceId = faceId; - } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java index 93a2913fbfa1..d21a7501e516 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java @@ -18,11 +18,12 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.face.Face; import android.os.IBinder; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.InternalCleanupClient; import com.android.server.biometrics.sensors.InternalEnumerateClient; @@ -40,29 +41,32 @@ class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsF FaceInternalCleanupClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner, - int sensorId, @NonNull List<Face> enrolledList, @NonNull BiometricUtils<Face> utils, - @NonNull Map<Integer, Long> authenticatorIds) { - super(context, lazyDaemon, userId, owner, sensorId, BiometricsProtoEnums.MODALITY_FACE, + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, @NonNull List<Face> enrolledList, + @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) { + super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, enrolledList, utils, authenticatorIds); } @Override protected InternalEnumerateClient<IBiometricsFace> getEnumerateClient(Context context, Supplier<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner, - List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId) { + List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner, - enrolledList, utils, sensorId); + enrolledList, utils, sensorId, logger, biometricContext); } @Override protected RemovalClient<Face, IBiometricsFace> getRemovalClient(Context context, Supplier<IBiometricsFace> lazyDaemon, IBinder token, int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) { // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove) // is all done internally. return new FaceRemovalClient(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils, - sensorId, authenticatorIds); + sensorId, logger, biometricContext, authenticatorIds); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java index f1788de38565..250dd7e0cdef 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java @@ -18,13 +18,14 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.face.Face; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.InternalEnumerateClient; @@ -41,9 +42,10 @@ class FaceInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFac FaceInternalEnumerateClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, @NonNull List<Face> enrolledList, - @NonNull BiometricUtils<Face> utils, int sensorId) { + @NonNull BiometricUtils<Face> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId, - BiometricsProtoEnums.MODALITY_FACE); + logger, biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java index cbc23e49f4d8..0ee7a354d5a4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java @@ -18,13 +18,14 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.face.Face; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.RemovalClient; @@ -44,9 +45,11 @@ class FaceRemovalClient extends RemovalClient<Face, IBiometricsFace> { FaceRemovalClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils, - int sensorId, @NonNull Map<Integer, Long> authenticatorIds) { - super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, - authenticatorIds, BiometricsProtoEnums.MODALITY_FACE); + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, + @NonNull Map<Integer, Long> authenticatorIds) { + super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, logger, + biometricContext, authenticatorIds); mBiometricId = biometricId; } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java index 88e2318b0570..6e74d3622c1a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java @@ -18,12 +18,13 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -42,10 +43,10 @@ public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> { FaceResetLockoutClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull byte[] hardwareAuthToken) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, - 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, logger, biometricContext); mHardwareAuthToken = new ArrayList<>(); for (byte b : hardwareAuthToken) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java index ab8d16145fab..b7b0dc046633 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.RevokeChallengeClient; import java.util.function.Supplier; @@ -37,8 +39,9 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<IBiometrics FaceRevokeChallengeClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, - int userId, @NonNull String owner, int sensorId) { - super(context, lazyDaemon, token, userId, owner, sensorId); + int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { + super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java index b2b52e713ec9..3c82f9c73700 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java @@ -18,7 +18,6 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.hardware.biometrics.face.V1_0.Status; import android.os.IBinder; @@ -26,6 +25,8 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -48,11 +49,11 @@ public class FaceSetFeatureClient extends HalClientMonitor<IBiometricsFace> { FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, - @NonNull String owner, int sensorId, int feature, boolean enabled, - byte[] hardwareAuthToken, int faceId) { + @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + int feature, boolean enabled, byte[] hardwareAuthToken, int faceId) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, - BiometricsProtoEnums.MODALITY_UNKNOWN, BiometricsProtoEnums.ACTION_UNKNOWN, - BiometricsProtoEnums.CLIENT_UNKNOWN); + logger, biometricContext); mFeature = feature; mEnabled = enabled; mFaceId = faceId; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java index 04b93274e42b..8385c3fa7103 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceUpdateActiveUserClient.java @@ -18,13 +18,14 @@ package com.android.server.biometrics.sensors.face.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.V1_0.IBiometricsFace; import android.os.Environment; import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -41,11 +42,11 @@ public class FaceUpdateActiveUserClient extends HalClientMonitor<IBiometricsFace FaceUpdateActiveUserClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner, - int sensorId, boolean hasEnrolledBiometrics, + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, - 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, logger, biometricContext); mHasEnrolledBiometrics = hasEnrolledBiometrics; mAuthenticatorIds = authenticatorIds; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 6366e19ef191..b4befd23671f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -83,6 +83,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.server.ServiceThread; import com.android.server.SystemService; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.LockoutTracker; @@ -815,7 +816,8 @@ public class FingerprintService extends SystemService { UserHandle.USER_CURRENT) != 0) { fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), mFingerprintStateCallback, hidlSensor, - mLockoutResetDispatcher, mGestureAvailabilityDispatcher); + mLockoutResetDispatcher, mGestureAvailabilityDispatcher, + BiometricContext.getInstance(getContext())); } else { fingerprint21 = Fingerprint21.newInstance(getContext(), mFingerprintStateCallback, hidlSensor, mHandler, @@ -843,7 +845,8 @@ public class FingerprintService extends SystemService { final FingerprintProvider provider = new FingerprintProvider(getContext(), mFingerprintStateCallback, props, instance, mLockoutResetDispatcher, - mGestureAvailabilityDispatcher); + mGestureAvailabilityDispatcher, + BiometricContext.getInstance(getContext())); mServiceProviders.add(provider); } catch (RemoteException e) { Slog.e(TAG, "Remote exception in getSensorProps: " + fqName); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java index 727101a69b06..55861bb4cc6f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java @@ -16,11 +16,11 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback; + import android.annotation.NonNull; import android.hardware.biometrics.fingerprint.ISession; -import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback; - /** * A holder for an AIDL {@link ISession} with additional metadata about the current user * and the backend. diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 2c1c80ccabb3..d26a78008529 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -23,10 +23,8 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.common.OperationContext; -import android.hardware.biometrics.common.OperationReason; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; @@ -35,6 +33,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthenticationClient; @@ -72,15 +72,18 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation, - int sensorId, boolean isStrongBiometric, int statsClient, + int sensorId, + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, + boolean isStrongBiometric, @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache, @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable ISidefpsController sidefpsController, boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps) { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner, - cookie, requireConfirmation, sensorId, isStrongBiometric, - BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener, + cookie, requireConfirmation, sensorId, + biometricLogger, biometricContext, + isStrongBiometric, taskStackListener, lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */, false /* isKeyguardBypassEnabled */); setRequestId(requestId); @@ -105,7 +108,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @NonNull @Override protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { - return new ClientMonitorCompositeCallback(mALSProbeCallback, callback); + return new ClientMonitorCompositeCallback(mALSProbeCallback, + getBiometricContextUnsubscriber(), callback); } @Override @@ -175,13 +179,17 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> final AidlSession session = getFreshDaemon(); if (session.hasContextMethods()) { - final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD - context.id = 0; - context.reason = OperationReason.UNKNOWN; - context.isAoD = false; - context.isCrypto = isCryptoOperation(); - return session.getSession().authenticateWithContext(mOperationId, context); + final OperationContext opContext = getOperationContext(); + final ICancellationSignal cancel = session.getSession().authenticateWithContext( + mOperationId, opContext); + getBiometricContext().subscribe(opContext, ctx -> { + try { + session.getSession().onContextChanged(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify context changed", e); + } + }); + return cancel; } else { return session.getSession().authenticate(mOperationId); } @@ -190,6 +198,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> @Override protected void stopHalOperation() { mSensorOverlays.hide(getSensorId()); + unsubscribeBiometricContext(); + if (mCancellationSignal != null) { try { mCancellationSignal.cancel(); @@ -219,7 +229,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> context.y = y; context.minor = minor; context.major = major; - context.isAoD = false; // TODO; get value + context.isAoD = getBiometricContext().isAoD(); session.getSession().onPointerDownWithContext(context); } else { session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major); @@ -277,8 +287,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED); // Lockout metrics are logged as an error code. final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; - getLogger().logOnError(getContext(), error, 0 /* vendorCode */, - isCryptoOperation(), getTargetUserId()); + getLogger().logOnError(getContext(), getOperationContext(), + error, 0 /* vendorCode */, getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); @@ -296,8 +306,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT); // Lockout metrics are logged as an error code. final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; - getLogger().logOnError(getContext(), error, 0 /* vendorCode */, - isCryptoOperation(), getTargetUserId()); + getLogger().logOnError(getContext(), getOperationContext(), + error, 0 /* vendorCode */, getTargetUserId()); try { getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index 6645332c1fac..0e89814c6ad2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -20,16 +20,15 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricOverlayConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; -import android.hardware.biometrics.common.OperationContext; -import android.hardware.biometrics.common.OperationReason; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -54,11 +53,10 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, int sensorId, - @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric, - int statsClient) { + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, + @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, - true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT, - BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); + true /* shouldVibrate */, biometricLogger, biometricContext); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/); @@ -99,13 +97,7 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements final AidlSession session = getFreshDaemon(); if (session.hasContextMethods()) { - final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD - context.id = 0; - context.reason = OperationReason.UNKNOWN; - context.isAoD = false; - context.isCrypto = isCryptoOperation(); - return session.getSession().detectInteractionWithContext(context); + return session.getSession().detectInteractionWithContext(getOperationContext()); } else { return session.getSession().detectInteraction(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index d0c5bb8851e6..e21d901b135d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -22,10 +22,8 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.common.ICancellationSignal; import android.hardware.biometrics.common.OperationContext; -import android.hardware.biometrics.common.OperationReason; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; @@ -38,6 +36,10 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.log.CallbackWithProbe; +import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -57,6 +59,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; @NonNull private final SensorOverlays mSensorOverlays; + @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback; private final @FingerprintManager.EnrollReason int mEnrollReason; @Nullable private ICancellationSignal mCancellationSignal; @@ -68,19 +71,22 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull FingerprintSensorPropertiesInternal sensorProps, @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable ISidefpsController sidefpsController, int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) { // UDFPS haptics occur when an image is acquired (instead of when the result is known) super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, - 0 /* timeoutSec */, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, - !sensorProps.isAnyUdfpsType() /* shouldVibrate */); + 0 /* timeoutSec */, sensorId, + !sensorProps.isAnyUdfpsType() /* shouldVibrate */, logger, biometricContext); setRequestId(requestId); mSensorProps = sensorProps; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); mMaxTemplatesPerUser = maxTemplatesPerUser; + mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */); + mEnrollReason = enrollReason; if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) { getLogger().disableMetrics(); @@ -90,8 +96,8 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps @NonNull @Override protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) { - return new ClientMonitorCompositeCallback( - getLogger().createALSCallback(true /* startWithClient */), callback); + return new ClientMonitorCompositeCallback(mALSProbeCallback, + getBiometricContextUnsubscriber(), callback); } @Override @@ -140,22 +146,6 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps } @Override - protected void stopHalOperation() { - mSensorOverlays.hide(getSensorId()); - - if (mCancellationSignal != null) { - try { - mCancellationSignal.cancel(); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when requesting cancel", e); - onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, - 0 /* vendorCode */); - mCallback.onClientFinished(this, false /* success */); - } - } - } - - @Override protected void startHalOperation() { mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this); @@ -176,22 +166,44 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken); if (session.hasContextMethods()) { - final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD - context.id = 0; - context.reason = OperationReason.UNKNOWN; - context.isAoD = false; - context.isCrypto = isCryptoOperation(); - return session.getSession().enrollWithContext(hat, context); + final OperationContext opContext = getOperationContext(); + final ICancellationSignal cancel = session.getSession().enrollWithContext( + hat, opContext); + getBiometricContext().subscribe(opContext, ctx -> { + try { + session.getSession().onContextChanged(ctx); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to notify context changed", e); + } + }); + return cancel; } else { return session.getSession().enroll(hat); } } @Override + protected void stopHalOperation() { + mSensorOverlays.hide(getSensorId()); + unsubscribeBiometricContext(); + + if (mCancellationSignal != null) { + try { + mCancellationSignal.cancel(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception when requesting cancel", e); + onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE, + 0 /* vendorCode */); + mCallback.onClientFinished(this, false /* success */); + } + } + } + + @Override public void onPointerDown(int x, int y, float minor, float major) { try { mIsPointerDown = true; + mALSProbeCallback.getProbe().enable(); final AidlSession session = getFreshDaemon(); if (session.hasContextMethods()) { @@ -201,7 +213,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps context.y = y; context.minor = minor; context.major = major; - context.isAoD = false; + context.isAoD = getBiometricContext().isAoD(); session.getSession().onPointerDownWithContext(context); } else { session.getSession().onPointerDown(0 /* pointerId */, x, y, minor, major); @@ -215,6 +227,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps public void onPointerUp() { try { mIsPointerDown = false; + mALSProbeCallback.getProbe().disable(); final AidlSession session = getFreshDaemon(); if (session.hasContextMethods()) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java index 04a7ca086ced..ddae8bedf7c1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.GenerateChallengeClient; @@ -38,8 +40,10 @@ class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSes @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, - int userId, @NonNull String owner, int sensorId) { - super(context, lazyDaemon, token, listener, userId, owner, sensorId); + int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext) { + super(context, lazyDaemon, token, listener, userId, owner, sensorId, + biometricLogger, biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java index 3a487fc98ca4..ea1a622c36ab 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGetAuthenticatorIdClient.java @@ -18,11 +18,12 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -36,11 +37,12 @@ class FingerprintGetAuthenticatorIdClient extends HalClientMonitor<AidlSession> private final Map<Integer, Long> mAuthenticatorIds; FingerprintGetAuthenticatorIdClient(@NonNull Context context, - @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, - int sensorId, Map<Integer, Long> authenticatorIds) { + @NonNull Supplier<AidlSession> lazyDaemon, + int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, + Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, - 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_FINGERPRINT, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, biometricLogger, biometricContext); mAuthenticatorIds = authenticatorIds; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java index 0ecad725cbeb..09bdd6de49f0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java @@ -22,6 +22,8 @@ import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.fingerprint.Fingerprint; import android.os.IBinder; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.InternalCleanupClient; import com.android.server.biometrics.sensors.InternalEnumerateClient; @@ -39,28 +41,35 @@ import java.util.function.Supplier; class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> { FingerprintInternalCleanupClient(@NonNull Context context, - @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner, - int sensorId, @NonNull List<Fingerprint> enrolledList, + @NonNull Supplier<AidlSession> lazyDaemon, + int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + @NonNull List<Fingerprint> enrolledList, @NonNull FingerprintUtils utils, @NonNull Map<Integer, Long> authenticatorIds) { - super(context, lazyDaemon, userId, owner, sensorId, - BiometricsProtoEnums.MODALITY_FINGERPRINT, enrolledList, utils, authenticatorIds); + super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, + enrolledList, utils, authenticatorIds); } @Override protected InternalEnumerateClient<AidlSession> getEnumerateClient(Context context, Supplier<AidlSession> lazyDaemon, IBinder token, int userId, String owner, - List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId) { + List<Fingerprint> enrolledList, BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner, - enrolledList, utils, sensorId); + enrolledList, utils, sensorId, + logger.swapAction(context, BiometricsProtoEnums.ACTION_ENUMERATE), + biometricContext); } @Override protected RemovalClient<Fingerprint, AidlSession> getRemovalClient(Context context, Supplier<AidlSession> lazyDaemon, IBinder token, int biometricId, int userId, String owner, BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) { return new FingerprintRemovalClient(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, new int[] {biometricId}, userId, owner, - utils, sensorId, authenticatorIds); + utils, sensorId, logger.swapAction(context, BiometricsProtoEnums.ACTION_REMOVE), + biometricContext, authenticatorIds); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java index 06ba6d45b407..a5a832aaaf59 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java @@ -18,12 +18,13 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.fingerprint.Fingerprint; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.InternalEnumerateClient; @@ -40,9 +41,10 @@ class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSes protected FingerprintInternalEnumerateClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, @NonNull List<Fingerprint> enrolledList, - @NonNull BiometricUtils<Fingerprint> utils, int sensorId) { + @NonNull BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId, - BiometricsProtoEnums.MODALITY_FINGERPRINT); + logger, biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java index 1ee32e986ca6..bc02897ef5c4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInvalidationClient.java @@ -23,6 +23,8 @@ import android.hardware.fingerprint.Fingerprint; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.InvalidationClient; import java.util.Map; @@ -33,8 +35,10 @@ public class FingerprintInvalidationClient extends InvalidationClient<Fingerprin public FingerprintInvalidationClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull Map<Integer, Long> authenticatorIds, @NonNull IInvalidationCallback callback) { - super(context, lazyDaemon, userId, sensorId, authenticatorIds, callback); + super(context, lazyDaemon, userId, sensorId, logger, biometricContext, + authenticatorIds, callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index efc93045f957..f810bca9707d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -26,6 +26,7 @@ import android.app.TaskStackListener; import android.content.Context; import android.content.pm.UserInfo; import android.content.res.TypedArray; +import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; @@ -53,6 +54,8 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -98,7 +101,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull private final BiometricTaskStackListener mTaskStackListener; // for requests that do not use biometric prompt @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0); - + @NonNull private final BiometricContext mBiometricContext; @Nullable private IFingerprint mDaemon; @Nullable private IUdfpsOverlayController mUdfpsOverlayController; @Nullable private ISidefpsController mSidefpsController; @@ -141,7 +144,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi @NonNull FingerprintStateCallback fingerprintStateCallback, @NonNull SensorProps[] props, @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { + @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull BiometricContext biometricContext) { mContext = context; mFingerprintStateCallback = fingerprintStateCallback; mHalInstanceName = halInstanceName; @@ -150,6 +154,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mLockoutResetDispatcher = lockoutResetDispatcher; mActivityTaskManager = ActivityTaskManager.getInstance(); mTaskStackListener = new BiometricTaskStackListener(); + mBiometricContext = biometricContext; final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context); @@ -181,7 +186,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi location.sensorRadius)) .collect(Collectors.toList())); final Sensor sensor = new Sensor(getTag() + "/" + sensorId, this, mContext, mHandler, - internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher); + internalProp, lockoutResetDispatcher, gestureAvailabilityDispatcher, + mBiometricContext); mSensors.put(sensorId, sensor); Slog.d(getTag(), "Added: " + internalProp); @@ -298,6 +304,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi new FingerprintGetAuthenticatorIdClient(mContext, mSensors.get(sensorId).getLazySession(), userId, mContext.getOpPackageName(), sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client); }); @@ -307,6 +316,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mHandler.post(() -> { final InvalidationRequesterClient<Fingerprint> client = new InvalidationRequesterClient<>(mContext, userId, sensorId, + BiometricLogger.ofUnknown(mContext), + mBiometricContext, FingerprintUtils.getInstance(sensorId)); scheduleForSensor(sensorId, client); }); @@ -317,7 +328,10 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mHandler.post(() -> { final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient( mContext, mSensors.get(sensorId).getLazySession(), userId, - mContext.getOpPackageName(), sensorId, hardwareAuthToken, + mContext.getOpPackageName(), sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, hardwareAuthToken, mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); scheduleForSensor(sensorId, client); }); @@ -331,7 +345,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi new FingerprintGenerateChallengeClient(mContext, mSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName, - sensorId); + sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext); scheduleForSensor(sensorId, client); }); } @@ -343,7 +359,10 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(mContext, mSensors.get(sensorId).getLazySession(), token, - userId, opPackageName, sensorId, challenge); + userId, opPackageName, sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, challenge); scheduleForSensor(sensorId, client); }); } @@ -361,6 +380,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mSensors.get(sensorId).getLazySession(), token, id, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mSensors.get(sensorId).getSensorProperties(), mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @@ -399,8 +421,10 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId); final FingerprintDetectClient client = new FingerprintDetectClient(mContext, mSensors.get(sensorId).getLazySession(), token, id, callback, userId, - opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric, - statsClient); + opPackageName, sensorId, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), + mBiometricContext, + mUdfpsOverlayController, isStrongBiometric); scheduleForSensor(sensorId, client, mFingerprintStateCallback); }); @@ -417,7 +441,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback, userId, operationId, restricted, opPackageName, cookie, - false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient, + false /* requireConfirmation */, sensorId, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), + mBiometricContext, isStrongBiometric, mTaskStackListener, mSensors.get(sensorId).getLockoutCache(), mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mSensors.get(sensorId).getSensorProperties()); @@ -479,6 +505,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mSensors.get(sensorId).getLazySession(), token, new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId, opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client, mFingerprintStateCallback); }); @@ -492,14 +521,22 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(mContext, mSensors.get(sensorId).getLazySession(), userId, - mContext.getOpPackageName(), sensorId, enrolledList, - FingerprintUtils.getInstance(sensorId), + mContext.getOpPackageName(), sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, + enrolledList, FingerprintUtils.getInstance(sensorId), mSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback, mFingerprintStateCallback)); }); } + private BiometricLogger createLogger(int statsAction, int statsClient) { + return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT, + statsAction, statsClient); + } + @Override public boolean isHardwareDetected(int sensorId) { return hasHalInstance(); @@ -524,6 +561,9 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi final FingerprintInvalidationClient client = new FingerprintInvalidationClient(mContext, mSensors.get(sensorId).getLazySession(), userId, sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mSensors.get(sensorId).getAuthenticatorIds(), callback); scheduleForSensor(sensorId, client); }); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java index fbc1dc08b726..d559bb1d72f1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java @@ -19,12 +19,13 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.fingerprint.Fingerprint; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.RemovalClient; @@ -45,9 +46,10 @@ class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> { @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, - authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT); + logger, biometricContext, authenticatorIds); mBiometricIds = biometricIds; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index 0e64dab5d325..f90cba79dac2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -18,7 +18,6 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.keymaster.HardwareAuthToken; import android.os.RemoteException; @@ -26,6 +25,8 @@ import android.util.Slog; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ErrorConsumer; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -50,11 +51,11 @@ class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implem FingerprintResetLockoutClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId, + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, @NonNull LockoutResetDispatcher lockoutResetDispatcher) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, - 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, biometricLogger, biometricContext); mHardwareAuthToken = HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken); mLockoutCache = lockoutTracker; mLockoutResetDispatcher = lockoutResetDispatcher; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java index fd938677105c..afa62e2e3173 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.RevokeChallengeClient; import java.util.function.Supplier; @@ -38,8 +40,10 @@ class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession FingerprintRevokeChallengeClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, - int userId, @NonNull String owner, int sensorId, long challenge) { - super(context, lazyDaemon, token, userId, owner, sensorId); + int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + long challenge) { + super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext); mChallenge = challenge; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java index 9dc06e1e0665..52305a31a644 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java @@ -27,6 +27,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.StartUserClient; @@ -40,9 +42,10 @@ public class FingerprintStartUserClient extends StartUserClient<IFingerprint, IS public FingerprintStartUserClient(@NonNull Context context, @NonNull Supplier<IFingerprint> lazyDaemon, @Nullable IBinder token, int userId, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull ISessionCallback sessionCallback, @NonNull UserStartedCallback<ISession> callback) { - super(context, lazyDaemon, token, userId, sensorId, callback); + super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback); mSessionCallback = sessionCallback; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java index fac17f2cc16a..2cc1879c0851 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.StopUserClient; @@ -33,8 +35,10 @@ public class FingerprintStopUserClient extends StopUserClient<AidlSession> { public FingerprintStopUserClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, @Nullable IBinder token, int userId, - int sensorId, @NonNull UserStoppedCallback callback) { - super(context, lazyDaemon, token, userId, sensorId, callback); + int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + @NonNull UserStoppedCallback callback) { + super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index 22762329926b..63e345e40ad7 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -39,12 +39,15 @@ import android.os.UserManager; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.biometrics.HardwareAuthTokenUtils; import com.android.server.biometrics.SensorServiceStateProto; import com.android.server.biometrics.SensorStateProto; import com.android.server.biometrics.UserStateProto; import com.android.server.biometrics.Utils; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; @@ -72,7 +75,7 @@ import java.util.function.Supplier; * {@link android.hardware.biometrics.fingerprint.IFingerprint} HAL. */ @SuppressWarnings("deprecation") -class Sensor { +public class Sensor { private boolean mTestHalEnabled; @@ -89,7 +92,8 @@ class Sensor { @Nullable private AidlSession mCurrentSession; @NonNull private final Supplier<AidlSession> mLazySession; - static class HalSessionCallback extends ISessionCallback.Stub { + @VisibleForTesting + public static class HalSessionCallback extends ISessionCallback.Stub { /** * Interface to sends results to the HalSessionCallback's owner. @@ -425,7 +429,8 @@ class Sensor { Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context, @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { + @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull BiometricContext biometricContext) { mTag = tag; mProvider = provider; mContext = context; @@ -442,7 +447,9 @@ class Sensor { @Override public StopUserClient<?> getStopUserClient(int userId) { return new FingerprintStopUserClient(mContext, mLazySession, mToken, - userId, mSensorProperties.sensorId, () -> mCurrentSession = null); + userId, mSensorProperties.sensorId, + BiometricLogger.ofUnknown(mContext), biometricContext, + () -> mCurrentSession = null); } @NonNull @@ -478,6 +485,7 @@ class Sensor { return new FingerprintStartUserClient(mContext, provider::getHalInstance, mToken, newUserId, mSensorProperties.sensorId, + BiometricLogger.ofUnknown(mContext), biometricContext, resultController, userStartedCallback); } }); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index 29d460fc1204..9d60859a4a21 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -57,6 +57,8 @@ import com.android.server.biometrics.Utils; import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto; import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto; import com.android.server.biometrics.fingerprint.PerformanceStatsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; @@ -118,6 +120,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider @NonNull private final HalResultController mHalResultController; @Nullable private IUdfpsOverlayController mUdfpsOverlayController; @Nullable private ISidefpsController mSidefpsController; + @NonNull private final BiometricContext mBiometricContext; // for requests that do not use biometric prompt @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0); private int mCurrentUserId = UserHandle.USER_NULL; @@ -318,15 +321,18 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider } } + @VisibleForTesting Fingerprint21(@NonNull Context context, @NonNull FingerprintStateCallback fingerprintStateCallback, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull BiometricScheduler scheduler, @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull HalResultController controller) { + @NonNull HalResultController controller, + @NonNull BiometricContext biometricContext) { mContext = context; mFingerprintStateCallback = fingerprintStateCallback; + mBiometricContext = biometricContext; mSensorProperties = sensorProps; mSensorId = sensorProps.sensorId; @@ -368,7 +374,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider final HalResultController controller = new HalResultController(sensorProps.sensorId, context, handler, scheduler); return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler, - lockoutResetDispatcher, controller); + lockoutResetDispatcher, controller, BiometricContext.getInstance(context)); } @Override @@ -493,6 +499,9 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider final FingerprintUpdateActiveUserClient client = new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override @@ -536,7 +545,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider // thread. mHandler.post(() -> { final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext, - userId, mContext.getOpPackageName(), sensorId, mLockoutTracker); + userId, mContext.getOpPackageName(), sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mLockoutTracker); mScheduler.scheduleClientMonitor(client); }); } @@ -548,7 +560,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider final FingerprintGenerateChallengeClient client = new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName, - mSensorProperties.sensorId); + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext); mScheduler.scheduleClientMonitor(client); }); } @@ -559,7 +574,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mHandler.post(() -> { final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient( mContext, mLazyDaemon, token, userId, opPackageName, - mSensorProperties.sensorId); + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext); mScheduler.scheduleClientMonitor(client); }); } @@ -577,7 +595,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, - mSensorProperties.sensorId, mUdfpsOverlayController, mSidefpsController, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENROLL, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, + mUdfpsOverlayController, mSidefpsController, enrollReason); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override @@ -616,8 +638,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); final FingerprintDetectClient client = new FingerprintDetectClient(mContext, mLazyDaemon, token, id, listener, userId, opPackageName, - mSensorProperties.sensorId, mUdfpsOverlayController, isStrongBiometric, - statsClient); + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), + mBiometricContext, mUdfpsOverlayController, + isStrongBiometric); mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback); }); @@ -636,7 +660,9 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( mContext, mLazyDaemon, token, requestId, listener, userId, operationId, restricted, opPackageName, cookie, false /* requireConfirmation */, - mSensorProperties.sensorId, isStrongBiometric, statsClient, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), + mBiometricContext, isStrongBiometric, mTaskStackListener, mLockoutTracker, mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mSensorProperties); @@ -678,7 +704,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId, userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), - mSensorProperties.sensorId, mAuthenticatorIds); + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback); }); } @@ -695,7 +724,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), 0 /* fingerprintId */, userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), - mSensorProperties.sensorId, mAuthenticatorIds); + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_REMOVE, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback); }); } @@ -709,7 +741,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mSensorProperties.sensorId, userId); final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( mContext, mLazyDaemon, userId, mContext.getOpPackageName(), - mSensorProperties.sensorId, enrolledList, + mSensorProperties.sensorId, + createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, + BiometricsProtoEnums.CLIENT_UNKNOWN), + mBiometricContext, enrolledList, FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, callback); }); @@ -722,6 +757,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider mFingerprintStateCallback)); } + private BiometricLogger createLogger(int statsAction, int statsClient) { + return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT, + statsAction, statsClient); + } + @Override public boolean isHardwareDetected(int sensorId) { return getDaemon() != null; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 1694bd92c73c..149526f21fdb 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -37,6 +37,7 @@ import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.R; +import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; @@ -247,7 +248,8 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage @NonNull FingerprintStateCallback fingerprintStateCallback, @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { + @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull BiometricContext biometricContext) { Slog.d(TAG, "Creating Fingerprint23Mock!"); final Handler handler = new Handler(Looper.getMainLooper()); @@ -256,7 +258,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage final MockHalResultController controller = new MockHalResultController(sensorProps.sensorId, context, handler, scheduler); return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler, - handler, lockoutResetDispatcher, controller); + handler, lockoutResetDispatcher, controller, biometricContext); } private static abstract class FakeFingerRunnable implements Runnable { @@ -385,9 +387,10 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage @NonNull TestableBiometricScheduler scheduler, @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull MockHalResultController controller) { + @NonNull MockHalResultController controller, + @NonNull BiometricContext biometricContext) { super(context, fingerprintStateCallback, sensorProps, scheduler, handler, - lockoutResetDispatcher, controller); + lockoutResetDispatcher, controller, biometricContext); mScheduler = scheduler; mScheduler.init(this); mHandler = handler; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java index 589bfcfba992..97fbb5f6e4fe 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java @@ -23,7 +23,6 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricFingerprintConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.ISidefpsController; @@ -32,6 +31,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.log.CallbackWithProbe; import com.android.server.biometrics.log.Probe; import com.android.server.biometrics.sensors.AuthenticationClient; @@ -69,7 +70,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId, boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation, - int sensorId, boolean isStrongBiometric, int statsClient, + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, boolean isStrongBiometric, @NonNull TaskStackListener taskStackListener, @NonNull LockoutFrameworkImpl lockoutTracker, @Nullable IUdfpsOverlayController udfpsOverlayController, @@ -77,10 +79,9 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi boolean allowBackgroundAuthentication, @NonNull FingerprintSensorPropertiesInternal sensorProps) { super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, - owner, cookie, requireConfirmation, sensorId, isStrongBiometric, - BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener, - lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */, - false /* isKeyguardBypassEnabled */); + owner, cookie, requireConfirmation, sensorId, logger, biometricContext, + isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication, + true /* shouldVibrate */, false /* isKeyguardBypassEnabled */); setRequestId(requestId); mLockoutFrameworkImpl = lockoutTracker; mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java index 88487462e51a..c2929d0f15b2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java @@ -22,7 +22,6 @@ import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricOverlayConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.IUdfpsOverlayController; import android.os.IBinder; @@ -30,6 +29,8 @@ import android.os.RemoteException; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.AcquisitionClient; import com.android.server.biometrics.sensors.AuthenticationConsumer; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -59,11 +60,11 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, - int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController, - boolean isStrongBiometric, int statsClient) { + int sensorId, + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, + @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) { super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId, - true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT, - BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); + true /* shouldVibrate */, biometricLogger, biometricContext); setRequestId(requestId); mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */); mIsStrongBiometric = isStrongBiometric; @@ -129,8 +130,9 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint> @Override public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated, ArrayList<Byte> hardwareAuthToken) { - getLogger().logOnAuthenticated(getContext(), authenticated, false /* requireConfirmation */, - isCryptoOperation(), getTargetUserId(), false /* isBiometricPrompt */); + getLogger().logOnAuthenticated(getContext(), getOperationContext(), + authenticated, false /* requireConfirmation */, + getTargetUserId(), false /* isBiometricPrompt */); // Do not distinguish between success/failures. vibrateSuccess(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java index c69deac371d9..1d478e53fd38 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricFingerprintConstants; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; @@ -31,6 +30,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -62,12 +63,13 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId, + @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, @Nullable IUdfpsOverlayController udfpsOverlayController, @Nullable ISidefpsController sidefpsController, @FingerprintManager.EnrollReason int enrollReason) { super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, - timeoutSec, BiometricsProtoEnums.MODALITY_FINGERPRINT, sensorId, - true /* shouldVibrate */); + timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger, + biometricContext); setRequestId(requestId); mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java index 591f542396ef..3bb7135a3e06 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.GenerateChallengeClient; @@ -41,8 +43,10 @@ public class FingerprintGenerateChallengeClient FingerprintGenerateChallengeClient(@NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner, - int sensorId) { - super(context, lazyDaemon, token, listener, userId, owner, sensorId); + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext) { + super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger, + biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java index 403602b8839d..5e7cf3578411 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java @@ -18,11 +18,12 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.Fingerprint; import android.os.IBinder; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.InternalCleanupClient; import com.android.server.biometrics.sensors.InternalEnumerateClient; @@ -42,31 +43,35 @@ class FingerprintInternalCleanupClient FingerprintInternalCleanupClient(@NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId, - @NonNull String owner, int sensorId, @NonNull List<Fingerprint> enrolledList, + @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + @NonNull List<Fingerprint> enrolledList, @NonNull BiometricUtils<Fingerprint> utils, @NonNull Map<Integer, Long> authenticatorIds) { - super(context, lazyDaemon, userId, owner, sensorId, - BiometricsProtoEnums.MODALITY_FINGERPRINT, enrolledList, utils, authenticatorIds); + super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext, + enrolledList, utils, authenticatorIds); } @Override protected InternalEnumerateClient<IBiometricsFingerprint> getEnumerateClient( Context context, Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token, int userId, String owner, List<Fingerprint> enrolledList, - BiometricUtils<Fingerprint> utils, int sensorId) { + BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner, - enrolledList, utils, sensorId); + enrolledList, utils, sensorId, logger, biometricContext); } @Override protected RemovalClient<Fingerprint, IBiometricsFingerprint> getRemovalClient(Context context, Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token, int biometricId, int userId, String owner, BiometricUtils<Fingerprint> utils, - int sensorId, Map<Integer, Long> authenticatorIds) { + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) { // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove) // is all done internally. return new FingerprintRemovalClient(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils, - sensorId, authenticatorIds); + sensorId, logger, biometricContext, authenticatorIds); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java index def8ed0735f5..0840f1b36903 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java @@ -18,13 +18,14 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.Fingerprint; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.InternalEnumerateClient; @@ -42,9 +43,10 @@ class FingerprintInternalEnumerateClient extends InternalEnumerateClient<IBiomet FingerprintInternalEnumerateClient(@NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, @NonNull List<Fingerprint> enrolledList, - @NonNull BiometricUtils<Fingerprint> utils, int sensorId) { + @NonNull BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId, - BiometricsProtoEnums.MODALITY_FINGERPRINT); + logger, biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java index 77c201c5ec97..9ec56c2a7dcd 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java @@ -18,13 +18,14 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.hardware.fingerprint.Fingerprint; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; import com.android.server.biometrics.sensors.RemovalClient; @@ -46,9 +47,10 @@ class FingerprintRemovalClient extends RemovalClient<Fingerprint, IBiometricsFin @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @NonNull Map<Integer, Long> authenticatorIds) { super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, - authenticatorIds, BiometricsProtoEnums.MODALITY_FINGERPRINT); + logger, biometricContext, authenticatorIds); mBiometricId = biometricId; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java index ed28e3ff481e..559ca0633c42 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java @@ -18,9 +18,10 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -33,10 +34,11 @@ public class FingerprintResetLockoutClient extends BaseClientMonitor { @NonNull final LockoutFrameworkImpl mLockoutTracker; public FingerprintResetLockoutClient(@NonNull Context context, int userId, - @NonNull String owner, int sensorId, @NonNull LockoutFrameworkImpl lockoutTracker) { + @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + @NonNull LockoutFrameworkImpl lockoutTracker) { super(context, null /* token */, null /* listener */, userId, owner, 0 /* cookie */, - sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + sensorId, logger, biometricContext); mLockoutTracker = lockoutTracker; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java index 0180a466d7d6..6273417eb0db 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.RevokeChallengeClient; import java.util.function.Supplier; @@ -39,8 +41,9 @@ public class FingerprintRevokeChallengeClient FingerprintRevokeChallengeClient(@NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token, - int userId, @NonNull String owner, int sensorId) { - super(context, lazyDaemon, token, userId, owner, sensorId); + int userId, @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) { + super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext); } @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java index cb9c33eb9956..a4e602553101 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java @@ -18,7 +18,6 @@ package com.android.server.biometrics.sensors.fingerprint.hidl; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; import android.os.Build; import android.os.Environment; @@ -27,6 +26,8 @@ import android.os.SELinux; import android.util.Slog; import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.HalClientMonitor; @@ -50,12 +51,13 @@ public class FingerprintUpdateActiveUserClient extends HalClientMonitor<IBiometr FingerprintUpdateActiveUserClient(@NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId, - @NonNull String owner, int sensorId, Supplier<Integer> currentUserId, + @NonNull String owner, int sensorId, + @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, + Supplier<Integer> currentUserId, boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds, boolean forceUpdateAuthenticatorId) { super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, - 0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN, - BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN); + 0 /* cookie */, sensorId, logger, biometricContext); mCurrentUserId = currentUserId; mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId; mHasEnrolledBiometrics = hasEnrolledBiometrics; diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 1e00ea9161a8..1b0341c1ce26 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -308,7 +308,8 @@ public class CameraServiceProxy extends SystemService public void onFixedRotationFinished(int displayId) { } @Override - public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearArea) { } + public void onKeepClearAreasChanged(int displayId, List<Rect> restricted, + List<Rect> unrestricted) { } } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index c2ca3a502153..d0e39ccb3214 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -23,7 +23,6 @@ import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STA import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS; import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE; import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED; -import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED; import android.annotation.IntDef; import android.annotation.IntRange; @@ -348,7 +347,7 @@ public final class DeviceStateManagerService extends SystemService { mBaseState = Optional.of(baseState); if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) { - mOverrideRequestController.cancelOverrideRequests(); + mOverrideRequestController.cancelOverrideRequest(); } mOverrideRequestController.handleBaseStateChanged(); updatePendingStateLocked(); @@ -503,7 +502,7 @@ public final class DeviceStateManagerService extends SystemService { @OverrideRequestController.RequestStatus int status) { if (status == STATUS_ACTIVE) { mActiveOverride = Optional.of(request); - } else if (status == STATUS_SUSPENDED || status == STATUS_CANCELED) { + } else if (status == STATUS_CANCELED) { if (mActiveOverride.isPresent() && mActiveOverride.get() == request) { mActiveOverride = Optional.empty(); } @@ -528,8 +527,6 @@ public final class DeviceStateManagerService extends SystemService { // Schedule the notification now. processRecord.notifyRequestActiveAsync(request.getToken()); } - } else if (status == STATUS_SUSPENDED) { - processRecord.notifyRequestSuspendedAsync(request.getToken()); } else { processRecord.notifyRequestCanceledAsync(request.getToken()); } @@ -595,15 +592,14 @@ public final class DeviceStateManagerService extends SystemService { } } - private void cancelRequestInternal(int callingPid, @NonNull IBinder token) { + private void cancelStateRequestInternal(int callingPid) { synchronized (mLock) { final ProcessRecord processRecord = mProcessRecords.get(callingPid); if (processRecord == null) { throw new IllegalStateException("Process " + callingPid + " has no registered callback."); } - - mOverrideRequestController.cancelRequest(token); + mActiveOverride.ifPresent(mOverrideRequestController::cancelRequest); } } @@ -628,6 +624,23 @@ public final class DeviceStateManagerService extends SystemService { } } + /** + * Allow top processes to request or cancel a device state change. If the calling process ID is + * not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission. + * @param callingPid + */ + private void checkCanControlDeviceState(int callingPid) { + // Allow top processes to request a device state change + // If the calling process ID is not the top app, then we check if this process + // holds a permission to CONTROL_DEVICE_STATE + final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp(); + if (topApp == null || topApp.getPid() != callingPid) { + getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, + "Permission required to request device state, " + + "or the call must come from the top focused app."); + } + } + private final class DeviceStateProviderListener implements DeviceStateProvider.Listener { @Override public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) { @@ -716,24 +729,6 @@ public final class DeviceStateManagerService extends SystemService { }); } - public void notifyRequestSuspendedAsync(IBinder token) { - @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token); - if (lastStatus != null - && (lastStatus == STATUS_SUSPENDED || lastStatus == STATUS_CANCELED)) { - return; - } - - mLastNotifiedStatus.put(token, STATUS_SUSPENDED); - mHandler.post(() -> { - try { - mCallback.onRequestSuspended(token); - } catch (RemoteException ex) { - Slog.w(TAG, "Failed to notify process " + mPid + " that request state changed.", - ex); - } - }); - } - public void notifyRequestCanceledAsync(IBinder token) { @RequestStatus Integer lastStatus = mLastNotifiedStatus.get(token); if (lastStatus != null && lastStatus == STATUS_CANCELED) { @@ -782,12 +777,7 @@ public final class DeviceStateManagerService extends SystemService { // Allow top processes to request a device state change // If the calling process ID is not the top app, then we check if this process // holds a permission to CONTROL_DEVICE_STATE - final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp(); - if (topApp.getPid() != callingPid) { - getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, - "Permission required to request device state, " - + "or the call must come from the top focused app."); - } + checkCanControlDeviceState(callingPid); if (token == null) { throw new IllegalArgumentException("Request token must not be null."); @@ -802,25 +792,16 @@ public final class DeviceStateManagerService extends SystemService { } @Override // Binder call - public void cancelRequest(IBinder token) { + public void cancelStateRequest() { final int callingPid = Binder.getCallingPid(); // Allow top processes to cancel a device state change // If the calling process ID is not the top app, then we check if this process // holds a permission to CONTROL_DEVICE_STATE - final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp(); - if (topApp.getPid() != callingPid) { - getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, - "Permission required to cancel device state, " - + "or the call must come from the top focused app."); - } - - if (token == null) { - throw new IllegalArgumentException("Request token must not be null."); - } + checkCanControlDeviceState(callingPid); final long callingIdentity = Binder.clearCallingIdentity(); try { - cancelRequestInternal(callingPid, token); + cancelStateRequestInternal(callingPid); } finally { Binder.restoreCallingIdentity(callingIdentity); } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java index eed68f8b1300..659ee75de453 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java @@ -97,7 +97,7 @@ public class DeviceStateManagerShellCommand extends ShellCommand { try { if ("reset".equals(nextArg)) { if (sLastRequest != null) { - mClient.cancelRequest(sLastRequest); + mClient.cancelStateRequest(); sLastRequest = null; } } else { @@ -105,9 +105,6 @@ public class DeviceStateManagerShellCommand extends ShellCommand { DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build(); mClient.requestState(request, null /* executor */, null /* callback */); - if (sLastRequest != null) { - mClient.cancelRequest(sLastRequest); - } sLastRequest = request; } diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java index 36cb4162accc..01f5a09323cf 100644 --- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java +++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java @@ -18,15 +18,13 @@ package com.android.server.devicestate; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.hardware.devicestate.DeviceStateRequest; import android.os.IBinder; +import android.util.Slog; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; /** * Manages the lifecycle of override requests. @@ -36,29 +34,26 @@ import java.util.List; * <ul> * <li>A new request is added with {@link #addRequest(OverrideRequest)}, in which case the * request will become suspended.</li> - * <li>The request is cancelled with {@link #cancelRequest(IBinder)} or as a side effect + * <li>The request is cancelled with {@link #cancelRequest} or as a side effect * of other methods calls, such as {@link #handleProcessDied(int)}.</li> * </ul> */ final class OverrideRequestController { + private static final String TAG = "OverrideRequestController"; + static final int STATUS_UNKNOWN = 0; /** * The request is the top-most request. */ static final int STATUS_ACTIVE = 1; /** - * The request is still present but is being superseded by another request. - */ - static final int STATUS_SUSPENDED = 2; - /** * The request is not longer valid. */ - static final int STATUS_CANCELED = 3; + static final int STATUS_CANCELED = 2; @IntDef(prefix = {"STATUS_"}, value = { STATUS_UNKNOWN, STATUS_ACTIVE, - STATUS_SUSPENDED, STATUS_CANCELED }) @Retention(RetentionPolicy.SOURCE) @@ -68,8 +63,6 @@ final class OverrideRequestController { switch (status) { case STATUS_ACTIVE: return "ACTIVE"; - case STATUS_SUSPENDED: - return "SUSPENDED"; case STATUS_CANCELED: return "CANCELED"; case STATUS_UNKNOWN: @@ -79,15 +72,13 @@ final class OverrideRequestController { } private final StatusChangeListener mListener; - private final List<OverrideRequest> mTmpRequestsToCancel = new ArrayList<>(); - // List of override requests with the most recent override request at the end. - private final ArrayList<OverrideRequest> mRequests = new ArrayList<>(); + // Handle to the current override request, null if none. + private OverrideRequest mRequest; private boolean mStickyRequestsAllowed; - // List of override requests that have outlived their process and will only be cancelled through - // a call to cancelStickyRequests(). - private final ArrayList<OverrideRequest> mStickyRequests = new ArrayList<>(); + // The current request has outlived their process. + private boolean mStickyRequest; OverrideRequestController(@NonNull StatusChangeListener listener) { mListener = listener; @@ -97,26 +88,26 @@ final class OverrideRequestController { * Sets sticky requests as either allowed or disallowed. When sticky requests are allowed a call * to {@link #handleProcessDied(int)} will not result in the request being cancelled * immediately. Instead, the request will be marked sticky and must be cancelled with a call - * to {@link #cancelStickyRequests()}. + * to {@link #cancelStickyRequest()}. */ void setStickyRequestsAllowed(boolean stickyRequestsAllowed) { mStickyRequestsAllowed = stickyRequestsAllowed; if (!mStickyRequestsAllowed) { - cancelStickyRequests(); + cancelStickyRequest(); } } /** - * Adds a request to the top of the stack and notifies the listener of all changes to request - * status as a result of this operation. + * Sets the new request as active and cancels the previous override request, notifies the + * listener of all changes to request status as a result of this operation. */ void addRequest(@NonNull OverrideRequest request) { - mRequests.add(request); + OverrideRequest previousRequest = mRequest; + mRequest = request; mListener.onStatusChanged(request, STATUS_ACTIVE); - if (mRequests.size() > 1) { - OverrideRequest prevRequest = mRequests.get(mRequests.size() - 2); - mListener.onStatusChanged(prevRequest, STATUS_SUSPENDED); + if (previousRequest != null) { + cancelRequestLocked(previousRequest); } } @@ -124,42 +115,32 @@ final class OverrideRequestController { * Cancels the request with the specified {@code token} and notifies the listener of all changes * to request status as a result of this operation. */ - void cancelRequest(@NonNull IBinder token) { - int index = getRequestIndex(token); - if (index == -1) { + void cancelRequest(@NonNull OverrideRequest request) { + // Either don't have a current request or attempting to cancel an already cancelled request + if (!hasRequest(request.getToken())) { return; } - - OverrideRequest request = mRequests.remove(index); - if (index == mRequests.size() && mRequests.size() > 0) { - // We removed the current active request so we need to set the new active request - // before cancelling this request. - OverrideRequest newTop = getLast(mRequests); - mListener.onStatusChanged(newTop, STATUS_ACTIVE); - } - mListener.onStatusChanged(request, STATUS_CANCELED); + cancelCurrentRequestLocked(); } /** - * Cancels all requests that are currently marked sticky and notifies the listener of all + * Cancels a request that is currently marked sticky and notifies the listener of all * changes to request status as a result of this operation. * * @see #setStickyRequestsAllowed(boolean) */ - void cancelStickyRequests() { - mTmpRequestsToCancel.clear(); - mTmpRequestsToCancel.addAll(mStickyRequests); - cancelRequestsLocked(mTmpRequestsToCancel); + void cancelStickyRequest() { + if (mStickyRequest) { + cancelCurrentRequestLocked(); + } } /** - * Cancels all override requests, this could be due to the device being put + * Cancels the current override request, this could be due to the device being put * into a hardware state that declares the flag "FLAG_CANCEL_OVERRIDE_REQUESTS" */ - void cancelOverrideRequests() { - mTmpRequestsToCancel.clear(); - mTmpRequestsToCancel.addAll(mRequests); - cancelRequestsLocked(mTmpRequestsToCancel); + void cancelOverrideRequest() { + cancelCurrentRequestLocked(); } /** @@ -167,7 +148,7 @@ final class OverrideRequestController { * {@code token}, {@code false} otherwise. */ boolean hasRequest(@NonNull IBinder token) { - return getRequestIndex(token) != -1; + return mRequest != null && token == mRequest.getToken(); } /** @@ -176,139 +157,79 @@ final class OverrideRequestController { * operation. */ void handleProcessDied(int pid) { - if (mRequests.isEmpty()) { + if (mRequest == null) { return; } - mTmpRequestsToCancel.clear(); - OverrideRequest prevActiveRequest = getLast(mRequests); - for (OverrideRequest request : mRequests) { - if (request.getPid() == pid) { - mTmpRequestsToCancel.add(request); + if (mRequest.getPid() == pid) { + if (mStickyRequestsAllowed) { + // Do not cancel the requests now because sticky requests are allowed. These + // requests will be cancelled on a call to cancelStickyRequests(). + mStickyRequest = true; + return; } + cancelCurrentRequestLocked(); } - - if (mStickyRequestsAllowed) { - // Do not cancel the requests now because sticky requests are allowed. These - // requests will be cancelled on a call to cancelStickyRequests(). - mStickyRequests.addAll(mTmpRequestsToCancel); - return; - } - - cancelRequestsLocked(mTmpRequestsToCancel); } /** * Notifies the controller that the base state has changed. The controller will notify the * listener of all changes to request status as a result of this change. - * - * @return {@code true} if calling this method has lead to a new active request, {@code false} - * otherwise. */ - boolean handleBaseStateChanged() { - if (mRequests.isEmpty()) { - return false; + void handleBaseStateChanged() { + if (mRequest == null) { + return; } - mTmpRequestsToCancel.clear(); - OverrideRequest prevActiveRequest = getLast(mRequests); - for (int i = 0; i < mRequests.size(); i++) { - OverrideRequest request = mRequests.get(i); - if ((request.getFlags() & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) { - mTmpRequestsToCancel.add(request); - } + if ((mRequest.getFlags() + & DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES) != 0) { + cancelCurrentRequestLocked(); } - - final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel); - return newActiveRequest; } /** * Notifies the controller that the set of supported states has changed. The controller will * notify the listener of all changes to request status as a result of this change. - * - * @return {@code true} if calling this method has lead to a new active request, {@code false} - * otherwise. */ - boolean handleNewSupportedStates(int[] newSupportedStates) { - if (mRequests.isEmpty()) { - return false; + void handleNewSupportedStates(int[] newSupportedStates) { + if (mRequest == null) { + return; } - mTmpRequestsToCancel.clear(); - for (int i = 0; i < mRequests.size(); i++) { - OverrideRequest request = mRequests.get(i); - if (!contains(newSupportedStates, request.getRequestedState())) { - mTmpRequestsToCancel.add(request); - } + if (!contains(newSupportedStates, mRequest.getRequestedState())) { + cancelCurrentRequestLocked(); } - - final boolean newActiveRequest = cancelRequestsLocked(mTmpRequestsToCancel); - return newActiveRequest; } void dumpInternal(PrintWriter pw) { - final int requestCount = mRequests.size(); + OverrideRequest overrideRequest = mRequest; + final boolean requestActive = overrideRequest != null; pw.println(); - pw.println("Override requests: size=" + requestCount); - for (int i = 0; i < requestCount; i++) { - OverrideRequest overrideRequest = mRequests.get(i); - int status = (i == requestCount - 1) ? STATUS_ACTIVE : STATUS_SUSPENDED; - pw.println(" " + i + ": mPid=" + overrideRequest.getPid() + pw.println("Override Request active: " + requestActive); + if (requestActive) { + pw.println("Request: mPid=" + overrideRequest.getPid() + ", mRequestedState=" + overrideRequest.getRequestedState() + ", mFlags=" + overrideRequest.getFlags() - + ", mStatus=" + statusToString(status)); + + ", mStatus=" + statusToString(STATUS_ACTIVE)); } } - /** - * Handles cancelling a set of requests. If the set of requests to cancel will lead to a new - * request becoming active this request will also be notified of its change in state. - * - * @return {@code true} if calling this method has lead to a new active request, {@code false} - * otherwise. - */ - private boolean cancelRequestsLocked(List<OverrideRequest> requestsToCancel) { - if (requestsToCancel.isEmpty()) { - return false; - } - - OverrideRequest prevActiveRequest = getLast(mRequests); - boolean causedNewRequestToBecomeActive = false; - mRequests.removeAll(requestsToCancel); - mStickyRequests.removeAll(requestsToCancel); - if (!mRequests.isEmpty()) { - OverrideRequest newActiveRequest = getLast(mRequests); - if (newActiveRequest != prevActiveRequest) { - mListener.onStatusChanged(newActiveRequest, STATUS_ACTIVE); - causedNewRequestToBecomeActive = true; - } - } - - for (int i = 0; i < requestsToCancel.size(); i++) { - mListener.onStatusChanged(requestsToCancel.get(i), STATUS_CANCELED); - } - return causedNewRequestToBecomeActive; + private void cancelRequestLocked(@NonNull OverrideRequest requestToCancel) { + mListener.onStatusChanged(requestToCancel, STATUS_CANCELED); } - private int getRequestIndex(@NonNull IBinder token) { - final int numberOfRequests = mRequests.size(); - if (numberOfRequests == 0) { - return -1; - } - - for (int i = 0; i < numberOfRequests; i++) { - OverrideRequest request = mRequests.get(i); - if (request.getToken() == token) { - return i; - } + /** + * Handles cancelling {@code mRequest}. + * Notifies the listener of the canceled status as well. + */ + private void cancelCurrentRequestLocked() { + if (mRequest == null) { + Slog.w(TAG, "Attempted to cancel a null OverrideRequest"); + return; } - return -1; - } - - @Nullable - private static <T> T getLast(List<T> list) { - return list.size() > 0 ? list.get(list.size() - 1) : null; + mStickyRequest = false; + mListener.onStatusChanged(mRequest, STATUS_CANCELED); + mRequest = null; } private static boolean contains(int[] array, int value) { diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 1b552577308d..162eb0e188b9 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -201,6 +201,10 @@ class AutomaticBrightnessController { // Controls High Brightness Mode. private HighBrightnessModeController mHbmController; + // Throttles (caps) maximum allowed brightness + private BrightnessThrottler mBrightnessThrottler; + private boolean mIsBrightnessThrottled; + // Context-sensitive brightness configurations require keeping track of the foreground app's // package name and category, which is done by registering a TaskStackListener to call back to // us onTaskStackChanged, and then using the ActivityTaskManager to get the foreground app's @@ -226,7 +230,7 @@ class AutomaticBrightnessController { long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, Context context, - HighBrightnessModeController hbmController, + HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler, BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, int ambientLightHorizonLong) { this(new Injector(), callbacks, looper, sensorManager, lightSensor, @@ -235,8 +239,8 @@ class AutomaticBrightnessController { lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig, darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig, ambientBrightnessThresholds, screenBrightnessThresholds, context, - hbmController, idleModeBrightnessMapper, ambientLightHorizonShort, - ambientLightHorizonLong + hbmController, brightnessThrottler, idleModeBrightnessMapper, + ambientLightHorizonShort, ambientLightHorizonLong ); } @@ -249,7 +253,7 @@ class AutomaticBrightnessController { long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds, HysteresisLevels screenBrightnessThresholds, Context context, - HighBrightnessModeController hbmController, + HighBrightnessModeController hbmController, BrightnessThrottler brightnessThrottler, BrightnessMappingStrategy idleModeBrightnessMapper, int ambientLightHorizonShort, int ambientLightHorizonLong) { mInjector = injector; @@ -291,6 +295,7 @@ class AutomaticBrightnessController { mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED; mHbmController = hbmController; + mBrightnessThrottler = brightnessThrottler; mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper; mIdleModeBrightnessMapper = idleModeBrightnessMapper; // Initialize to active (normal) screen brightness mode @@ -365,6 +370,13 @@ class AutomaticBrightnessController { prepareBrightnessAdjustmentSample(); } changed |= setLightSensorEnabled(enable && !dozing); + + if (mIsBrightnessThrottled != mBrightnessThrottler.isThrottled()) { + // Maximum brightness has changed, so recalculate display brightness. + mIsBrightnessThrottled = mBrightnessThrottler.isThrottled(); + changed = true; + } + if (changed) { updateAutoBrightness(false /*sendUpdate*/, userInitiatedChange); } @@ -855,8 +867,11 @@ class AutomaticBrightnessController { // Clamps values with float range [0.0-1.0] private float clampScreenBrightness(float value) { - return MathUtils.constrain(value, - mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax()); + final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(), + mBrightnessThrottler.getBrightnessCap()); + final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(), + mBrightnessThrottler.getBrightnessCap()); + return MathUtils.constrain(value, minBrightness, maxBrightness); } private void prepareBrightnessAdjustmentSample() { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 7ad497972462..4e88acd11fac 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -141,7 +141,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; @@ -1756,10 +1755,6 @@ public final class DisplayManagerService extends SystemService { void setUserPreferredDisplayModeInternal(int displayId, Display.Mode mode) { synchronized (mSyncRoot) { - if (Objects.equals(mUserPreferredMode, mode) && displayId == Display.INVALID_DISPLAY) { - return; - } - if (mode != null && !isResolutionAndRefreshRateValid(mode) && displayId == Display.INVALID_DISPLAY) { throw new IllegalArgumentException("width, height and refresh rate of mode should " @@ -1813,7 +1808,15 @@ public final class DisplayManagerService extends SystemService { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.USER_PREFERRED_RESOLUTION_WIDTH, resolutionWidth); mDisplayDeviceRepo.forEachLocked((DisplayDevice device) -> { - device.setUserPreferredDisplayModeLocked(mode); + // If there is a display specific mode, don't override that + final Point deviceUserPreferredResolution = + mPersistentDataStore.getUserPreferredResolution(device); + final float deviceRefreshRate = + mPersistentDataStore.getUserPreferredRefreshRate(device); + if (!isValidResolution(deviceUserPreferredResolution) + && !isValidRefreshRate(deviceRefreshRate)) { + device.setUserPreferredDisplayModeLocked(mode); + } }); } @@ -3533,6 +3536,14 @@ public final class DisplayManagerService extends SystemService { && (brightness <= PowerManager.BRIGHTNESS_MAX); } + private static boolean isValidResolution(Point resolution) { + return (resolution != null) && (resolution.x > 0) && (resolution.y > 0); + } + + private static boolean isValidRefreshRate(float refreshRate) { + return !Float.isNaN(refreshRate) && (refreshRate > 0.0f); + } + private final class LocalService extends DisplayManagerInternal { @Override diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 1f448549c59c..6ae1a5a96a24 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -974,7 +974,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, screenBrightnessThresholds, mContext, - mHbmController, mIdleModeBrightnessMapper, + mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper, mDisplayDeviceConfig.getAmbientHorizonShort(), mDisplayDeviceConfig.getAmbientHorizonLong()); } else { @@ -1014,6 +1014,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode(); } } + if (mDisplayWhiteBalanceController != null) { + mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle); + } } private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() { @@ -1344,6 +1347,29 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL); } + // Now that a desired brightness has been calculated, apply brightness throttling. The + // dimming and low power transformations that follow can only dim brightness further. + // + // We didn't do this earlier through brightness clamping because we need to know both + // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations. + // Note throttling effectively changes the allowed brightness range, so, similarly to HBM, + // we broadcast this change through setting. + final float unthrottledBrightnessState = brightnessState; + if (mBrightnessThrottler.isThrottled()) { + brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap()); + mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED); + if (!mAppliedThrottling) { + // Brightness throttling is needed, so do so quickly. + // Later, when throttling is removed, we let other mechanisms decide on speed. + slowChange = false; + updateScreenBrightnessSetting = true; + } + mAppliedThrottling = true; + } else if (mAppliedThrottling) { + mAppliedThrottling = false; + updateScreenBrightnessSetting = true; + } + if (updateScreenBrightnessSetting) { // Tell the rest of the system about the new brightness in case we had to change it // for things like auto-brightness or high-brightness-mode. Note that we do this @@ -1390,20 +1416,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAppliedLowPower = false; } - // Apply brightness throttling after applying all other transforms - final float unthrottledBrightnessState = brightnessState; - if (mBrightnessThrottler.isThrottled()) { - brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap()); - mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED); - if (!mAppliedThrottling) { - slowChange = false; - } - mAppliedThrottling = true; - } else if (mAppliedThrottling) { - slowChange = false; - mAppliedThrottling = false; - } - // The current brightness to use has been calculated at this point, and HbmController should // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it // here instead of having HbmController listen to the brightness setting because certain @@ -1653,6 +1665,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) { synchronized (mCachedBrightnessInfo) { + final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(), + mBrightnessThrottler.getBrightnessCap()); + final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(), + mBrightnessThrottler.getBrightnessCap()); boolean changed = false; changed |= @@ -1663,10 +1679,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call adjustedBrightness); changed |= mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin, - mHbmController.getCurrentBrightnessMin()); + minBrightness); changed |= mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax, - mHbmController.getCurrentBrightnessMax()); + maxBrightness); changed |= mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode, mHbmController.getHighBrightnessMode()); diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index 534ed5d8491a..23c17f5af10d 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -521,6 +521,10 @@ class HighBrightnessModeController { } else if (mIsBlockedByLowPowerMode) { reason = FrameworkStatsLog .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON; + } else if (mBrightness <= mHbmData.transitionPoint) { + // This must be after external thermal check. + reason = FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS; } } diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java index 151ec8183269..fb36dc792a67 100644 --- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java +++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceController.java @@ -44,26 +44,18 @@ public class DisplayWhiteBalanceController implements AmbientSensor.AmbientBrightnessSensor.Callbacks, AmbientSensor.AmbientColorTemperatureSensor.Callbacks { - protected static final String TAG = "DisplayWhiteBalanceController"; - protected boolean mLoggingEnabled; + private static final String TAG = "DisplayWhiteBalanceController"; + private boolean mLoggingEnabled; - private boolean mEnabled; - - // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC - // implements Callbacks and passes itself to the DWBC so it can call back into it without - // knowing about it. - private Callbacks mCallbacks; - - private AmbientSensor.AmbientBrightnessSensor mBrightnessSensor; + private final ColorDisplayServiceInternal mColorDisplayServiceInternal; + private final AmbientSensor.AmbientBrightnessSensor mBrightnessSensor; @VisibleForTesting AmbientFilter mBrightnessFilter; - private AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor; - + private final AmbientSensor.AmbientColorTemperatureSensor mColorTemperatureSensor; @VisibleForTesting AmbientFilter mColorTemperatureFilter; - private DisplayWhiteBalanceThrottler mThrottler; - + private final DisplayWhiteBalanceThrottler mThrottler; // In low brightness conditions the ALS readings are more noisy and produce // high errors. This default is introduced to provide a fixed display color // temperature when sensor readings become unreliable. @@ -74,16 +66,12 @@ public class DisplayWhiteBalanceController implements private final float mHighLightAmbientColorTemperature; private float mAmbientColorTemperature; - @VisibleForTesting float mPendingAmbientColorTemperature; private float mLastAmbientColorTemperature; - private ColorDisplayServiceInternal mColorDisplayServiceInternal; - // The most recent ambient color temperature values are kept for debugging purposes. - private static final int HISTORY_SIZE = 50; - private History mAmbientColorTemperatureHistory; + private final History mAmbientColorTemperatureHistory; // Override the ambient color temperature for debugging purposes. private float mAmbientColorTemperatureOverride; @@ -91,6 +79,10 @@ public class DisplayWhiteBalanceController implements // A piecewise linear relationship between ambient and display color temperatures. private Spline.LinearSpline mAmbientToDisplayColorTemperatureSpline; + // A piecewise linear relationship between ambient and display color temperatures, with a + // stronger change between the two sets of values. + private Spline.LinearSpline mStrongAmbientToDisplayColorTemperatureSpline; + // In very low or very high brightness conditions Display White Balance should // be to set to a default instead of using mAmbientToDisplayColorTemperatureSpline. // However, setting Display White Balance based on thresholds can cause the @@ -109,6 +101,17 @@ public class DisplayWhiteBalanceController implements private float mLatestLowLightBias; private float mLatestHighLightBias; + private boolean mEnabled; + + // Whether a higher-strength adjustment should be applied; this must be enabled in addition to + // mEnabled in order to be applied. + private boolean mStrongModeEnabled; + + // To decouple the DisplayPowerController from the DisplayWhiteBalanceController, the DPC + // implements Callbacks and passes itself to the DWBC so it can call back into it without + // knowing about it. + private Callbacks mDisplayPowerControllerCallbacks; + /** * @param brightnessSensor * The sensor used to detect changes in the ambient brightness. @@ -159,16 +162,18 @@ public class DisplayWhiteBalanceController implements @NonNull AmbientSensor.AmbientColorTemperatureSensor colorTemperatureSensor, @NonNull AmbientFilter colorTemperatureFilter, @NonNull DisplayWhiteBalanceThrottler throttler, - float[] lowLightAmbientBrightnesses, float[] lowLightAmbientBiases, + float[] lowLightAmbientBrightnesses, + float[] lowLightAmbientBiases, float lowLightAmbientColorTemperature, - float[] highLightAmbientBrightnesses, float[] highLightAmbientBiases, + float[] highLightAmbientBrightnesses, + float[] highLightAmbientBiases, float highLightAmbientColorTemperature, - float[] ambientColorTemperatures, float[] displayColorTemperatures) { + float[] ambientColorTemperatures, + float[] displayColorTemperatures, + float[] strongAmbientColorTemperatures, + float[] strongDisplayColorTemperatures) { validateArguments(brightnessSensor, brightnessFilter, colorTemperatureSensor, colorTemperatureFilter, throttler); - mLoggingEnabled = false; - mEnabled = false; - mCallbacks = null; mBrightnessSensor = brightnessSensor; mBrightnessFilter = brightnessFilter; mColorTemperatureSensor = colorTemperatureSensor; @@ -179,7 +184,7 @@ public class DisplayWhiteBalanceController implements mAmbientColorTemperature = -1.0f; mPendingAmbientColorTemperature = -1.0f; mLastAmbientColorTemperature = -1.0f; - mAmbientColorTemperatureHistory = new History(HISTORY_SIZE); + mAmbientColorTemperatureHistory = new History(/* size= */ 50); mAmbientColorTemperatureOverride = -1.0f; try { @@ -235,6 +240,13 @@ public class DisplayWhiteBalanceController implements mAmbientToDisplayColorTemperatureSpline = null; } + try { + mStrongAmbientToDisplayColorTemperatureSpline = new Spline.LinearSpline( + strongAmbientColorTemperatures, strongDisplayColorTemperatures); + } catch (Exception e) { + Slog.e(TAG, "Failed to create strong ambient to display color temperature spline", e); + } + mColorDisplayServiceInternal = LocalServices.getService(ColorDisplayServiceInternal.class); } @@ -255,6 +267,19 @@ public class DisplayWhiteBalanceController implements } /** + * Enable/disable the stronger adjustment option. + * + * @param enabled whether the stronger adjustment option should be turned on + */ + public void setStrongModeEnabled(boolean enabled) { + mStrongModeEnabled = enabled; + if (mEnabled) { + updateAmbientColorTemperature(); + updateDisplayColorTemperature(); + } + } + + /** * Set an object to call back to when the display color temperature should be updated. * * @param callbacks @@ -263,10 +288,10 @@ public class DisplayWhiteBalanceController implements * @return Whether the method succeeded or not. */ public boolean setCallbacks(Callbacks callbacks) { - if (mCallbacks == callbacks) { + if (mDisplayPowerControllerCallbacks == callbacks) { return false; } - mCallbacks = callbacks; + mDisplayPowerControllerCallbacks = callbacks; return true; } @@ -321,7 +346,7 @@ public class DisplayWhiteBalanceController implements writer.println("DisplayWhiteBalanceController"); writer.println(" mLoggingEnabled=" + mLoggingEnabled); writer.println(" mEnabled=" + mEnabled); - writer.println(" mCallbacks=" + mCallbacks); + writer.println(" mDisplayPowerControllerCallbacks=" + mDisplayPowerControllerCallbacks); mBrightnessSensor.dump(writer); mBrightnessFilter.dump(writer); mColorTemperatureSensor.dump(writer); @@ -336,6 +361,8 @@ public class DisplayWhiteBalanceController implements writer.println(" mAmbientColorTemperatureOverride=" + mAmbientColorTemperatureOverride); writer.println(" mAmbientToDisplayColorTemperatureSpline=" + mAmbientToDisplayColorTemperatureSpline); + writer.println(" mStrongAmbientToDisplayColorTemperatureSpline=" + + mStrongAmbientToDisplayColorTemperatureSpline); writer.println(" mLowLightAmbientBrightnessToBiasSpline=" + mLowLightAmbientBrightnessToBiasSpline); writer.println(" mHighLightAmbientBrightnessToBiasSpline=" @@ -364,9 +391,20 @@ public class DisplayWhiteBalanceController implements float ambientColorTemperature = mColorTemperatureFilter.getEstimate(time); mLatestAmbientColorTemperature = ambientColorTemperature; - if (mAmbientToDisplayColorTemperatureSpline != null && ambientColorTemperature != -1.0f) { - ambientColorTemperature = - mAmbientToDisplayColorTemperatureSpline.interpolate(ambientColorTemperature); + if (mStrongModeEnabled) { + if (mStrongAmbientToDisplayColorTemperatureSpline != null + && ambientColorTemperature != -1.0f) { + ambientColorTemperature = + mStrongAmbientToDisplayColorTemperatureSpline.interpolate( + ambientColorTemperature); + } + } else { + if (mAmbientToDisplayColorTemperatureSpline != null + && ambientColorTemperature != -1.0f) { + ambientColorTemperature = + mAmbientToDisplayColorTemperatureSpline.interpolate( + ambientColorTemperature); + } } float ambientBrightness = mBrightnessFilter.getEstimate(time); @@ -409,8 +447,8 @@ public class DisplayWhiteBalanceController implements Slog.d(TAG, "pending ambient color temperature: " + ambientColorTemperature); } mPendingAmbientColorTemperature = ambientColorTemperature; - if (mCallbacks != null) { - mCallbacks.updateWhiteBalance(); + if (mDisplayPowerControllerCallbacks != null) { + mDisplayPowerControllerCallbacks.updateWhiteBalance(); } } diff --git a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java index a72b1ed8f3ec..07821b0a984e 100644 --- a/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java +++ b/services/core/java/com/android/server/display/whitebalance/DisplayWhiteBalanceFactory.java @@ -87,15 +87,22 @@ public class DisplayWhiteBalanceFactory { .config_displayWhiteBalanceHighLightAmbientColorTemperature); final float[] ambientColorTemperatures = getFloatArray(resources, com.android.internal.R.array.config_displayWhiteBalanceAmbientColorTemperatures); - final float[] displayColorTempeartures = getFloatArray(resources, + final float[] displayColorTemperatures = getFloatArray(resources, com.android.internal.R.array.config_displayWhiteBalanceDisplayColorTemperatures); + final float[] strongAmbientColorTemperatures = getFloatArray(resources, + com.android.internal.R.array + .config_displayWhiteBalanceStrongAmbientColorTemperatures); + final float[] strongDisplayColorTemperatures = getFloatArray(resources, + com.android.internal.R.array + .config_displayWhiteBalanceStrongDisplayColorTemperatures); final DisplayWhiteBalanceController controller = new DisplayWhiteBalanceController( brightnessSensor, brightnessFilter, colorTemperatureSensor, colorTemperatureFilter, throttler, displayWhiteBalanceLowLightAmbientBrightnesses, displayWhiteBalanceLowLightAmbientBiases, lowLightAmbientColorTemperature, displayWhiteBalanceHighLightAmbientBrightnesses, displayWhiteBalanceHighLightAmbientBiases, highLightAmbientColorTemperature, - ambientColorTemperatures, displayColorTempeartures); + ambientColorTemperatures, displayColorTemperatures, strongAmbientColorTemperatures, + strongDisplayColorTemperatures); brightnessSensor.setCallbacks(controller); colorTemperatureSensor.setCallbacks(controller); return controller; diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 611b28850efe..f0a6af3c8834 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -416,13 +416,14 @@ public final class DreamManagerService extends SystemService { mCurrentDreamCanDoze = canDoze; mCurrentDreamUserId = userId; + if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) { + mUiEventLogger.log(DreamManagerEvent.DREAM_START); + } + PowerManager.WakeLock wakeLock = mPowerManager .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream"); mHandler.post(wakeLock.wrap(() -> { mAtmInternal.notifyDreamStateChanged(true); - if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) { - mUiEventLogger.log(DreamManagerEvent.DREAM_START); - } mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock, mDreamOverlayServiceName); })); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index c674ffebfe92..454a76ac04f4 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -901,17 +901,12 @@ abstract class HdmiCecLocalDevice { protected int handleVendorCommandWithId(HdmiCecMessage message) { byte[] params = message.getParams(); int vendorId = HdmiUtils.threeBytesToInt(params); - if (vendorId == mService.getVendorId()) { - if (!mService.invokeVendorCommandListenersOnReceived( - mDeviceType, message.getSource(), message.getDestination(), params, true)) { - return Constants.ABORT_REFUSED; - } - } else if (message.getDestination() != Constants.ADDR_BROADCAST - && message.getSource() != Constants.ADDR_UNREGISTERED) { - Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>"); - return Constants.ABORT_UNRECOGNIZED_OPCODE; - } else { + if (message.getDestination() == Constants.ADDR_BROADCAST + || message.getSource() == Constants.ADDR_UNREGISTERED) { Slog.v(TAG, "Wrong broadcast vendor command. Ignoring"); + } else if (!mService.invokeVendorCommandListenersOnReceived( + mDeviceType, message.getSource(), message.getDestination(), params, true)) { + return Constants.ABORT_REFUSED; } return Constants.HANDLED; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index b23395f36e63..f413fbd5c9b2 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -17,10 +17,15 @@ package com.android.server.hdmi; import android.annotation.CallSuper; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.os.Binder; import android.os.Handler; import android.os.PowerManager; import android.os.PowerManager.WakeLock; @@ -408,7 +413,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { // locale from being chosen. 'eng' in the CEC command, for instance, // will always be mapped to en-AU among other variants like en-US, en-GB, // an en-IN, which may not be the expected one. - LocalePicker.updateLocale(localeInfo.getLocale()); + startSetMenuLanguageActivity(localeInfo.getLocale()); return Constants.HANDLED; } } @@ -420,6 +425,24 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { } } + private void startSetMenuLanguageActivity(Locale locale) { + final long identity = Binder.clearCallingIdentity(); + try { + Context context = mService.getContext(); + Intent intent = new Intent(); + intent.putExtra(HdmiControlManager.EXTRA_LOCALE, locale.toLanguageTag()); + intent.setComponent( + ComponentName.unflattenFromString(context.getResources().getString( + com.android.internal.R.string.config_hdmiCecSetMenuLanguageActivity))); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivityAsUser(intent, context.getUser()); + } catch (ActivityNotFoundException e) { + Slog.e(TAG, "unable to start HdmiCecSetMenuLanguageActivity"); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override @Constants.HandleMessageResult protected int handleSetSystemAudioMode(HdmiCecMessage message) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java index 6f7473d60121..57fe9e6a4acc 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecStandbyModeHandler.java @@ -133,6 +133,7 @@ public final class HdmiCecStandbyModeHandler { addHandler(Constants.MESSAGE_SET_OSD_NAME, mBypasser); addHandler(Constants.MESSAGE_DEVICE_VENDOR_ID, mBypasser); addHandler(Constants.MESSAGE_REPORT_POWER_STATUS, mBypasser); + addHandler(Constants.MESSAGE_GIVE_FEATURES, mBypasser); addHandler(Constants.MESSAGE_USER_CONTROL_PRESSED, mUserControlProcessedHandler); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 8391e0b4e19a..8ac233114b48 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1701,11 +1701,11 @@ public class HdmiControlService extends SystemService { class VendorCommandListenerRecord implements IBinder.DeathRecipient { private final IHdmiVendorCommandListener mListener; - private final int mDeviceType; + private final int mVendorId; - public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) { + VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int vendorId) { mListener = listener; - mDeviceType = deviceType; + mVendorId = vendorId; } @Override @@ -2191,10 +2191,10 @@ public class HdmiControlService extends SystemService { } @Override - public void addVendorCommandListener(final IHdmiVendorCommandListener listener, - final int deviceType) { + public void addVendorCommandListener( + final IHdmiVendorCommandListener listener, final int vendorId) { initBinderCall(); - HdmiControlService.this.addVendorCommandListener(listener, deviceType); + HdmiControlService.this.addVendorCommandListener(listener, vendorId); } @Override @@ -3354,8 +3354,9 @@ public class HdmiControlService extends SystemService { mStandbyMessageReceived = false; } - private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { - VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); + @VisibleForTesting + void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) { + VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, vendorId); try { listener.asBinder().linkToDeath(record, 0); } catch (RemoteException e) { @@ -3374,8 +3375,14 @@ public class HdmiControlService extends SystemService { return false; } for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { - if (record.mDeviceType != deviceType) { - continue; + if (hasVendorId) { + int vendorId = + ((params[0] & 0xFF) << 16) + + ((params[1] & 0xFF) << 8) + + (params[2] & 0xFF); + if (record.mVendorId != vendorId) { + continue; + } } try { record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId); diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index de933cc47005..783a88ca29bf 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -2285,14 +2285,8 @@ public class InputManagerService extends IInputManager.Stub nativeNotifyPortAssociationsChanged(mPtr); } - /** - * Add a runtime association between the input device name and the display unique id. - * @param inputDeviceName The name of the input device. - * @param displayUniqueId The unique id of the associated display. - */ @Override // Binder call - public void addUniqueIdAssociation(@NonNull String inputDeviceName, - @NonNull String displayUniqueId) { + public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) { if (!checkCallingPermission( android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY, "addNameAssociation()")) { @@ -2300,20 +2294,16 @@ public class InputManagerService extends IInputManager.Stub "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission"); } - Objects.requireNonNull(inputDeviceName); + Objects.requireNonNull(inputPort); Objects.requireNonNull(displayUniqueId); synchronized (mAssociationsLock) { - mUniqueIdAssociations.put(inputDeviceName, displayUniqueId); + mUniqueIdAssociations.put(inputPort, displayUniqueId); } nativeChangeUniqueIdAssociation(mPtr); } - /** - * Remove the runtime association between the input device and the display. - * @param inputDeviceName The port of the input device to be cleared. - */ @Override // Binder call - public void removeUniqueIdAssociation(@NonNull String inputDeviceName) { + public void removeUniqueIdAssociation(@NonNull String inputPort) { if (!checkCallingPermission( android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY, "removeUniqueIdAssociation()")) { @@ -2321,9 +2311,9 @@ public class InputManagerService extends IInputManager.Stub "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission"); } - Objects.requireNonNull(inputDeviceName); + Objects.requireNonNull(inputPort); synchronized (mAssociationsLock) { - mUniqueIdAssociations.remove(inputDeviceName); + mUniqueIdAssociations.remove(inputPort); } nativeChangeUniqueIdAssociation(mPtr); } @@ -2594,6 +2584,13 @@ public class InputManagerService extends IInputManager.Stub pw.println(" display: " + v); }); } + if (!mUniqueIdAssociations.isEmpty()) { + pw.println("Unique Id Associations:"); + mUniqueIdAssociations.forEach((k, v) -> { + pw.print(" port: " + k); + pw.println(" uniqueId: " + v); + }); + } } } diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index c87ca92d632e..6cb3b3b6740d 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -107,9 +107,11 @@ final class IInputMethodInvoker { @AnyThread void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, - int configChanges, boolean stylusHwSupported) { + int configChanges, boolean stylusHwSupported, + boolean shouldShowImeSwitcherWhenImeIsShown) { try { - mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported); + mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported, + shouldShowImeSwitcherWhenImeIsShown); } catch (RemoteException e) { logRemoteException(e); } @@ -145,9 +147,20 @@ final class IInputMethodInvoker { @AnyThread void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute, - boolean restarting) { + boolean restarting, boolean shouldShowImeSwitcherWhenImeIsShown) { try { - mTarget.startInput(startInputToken, inputContext, attribute, restarting); + mTarget.startInput(startInputToken, inputContext, attribute, restarting, + shouldShowImeSwitcherWhenImeIsShown); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void onShouldShowImeSwitcherWhenImeIsShownChanged(boolean shouldShowImeSwitcherWhenImeIsShown) { + try { + mTarget.onShouldShowImeSwitcherWhenImeIsShownChanged( + shouldShowImeSwitcherWhenImeIsShown); } catch (RemoteException e) { logRemoteException(e); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 936b1a20e326..c207738a4f88 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2325,10 +2325,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub true /* direct */); } + final boolean shouldShowImeSwitcherWhenImeIsShown = + shouldShowImeSwitcherWhenImeIsShownLocked(); final SessionState session = mCurClient.curSession; setEnabledSessionLocked(session); - session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting); - + session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting, + shouldShowImeSwitcherWhenImeIsShown); if (mShowRequested) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null, @@ -2528,7 +2530,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + mCurTokenDisplayId); } inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token), - configChanges, supportStylusHw); + configChanges, supportStylusHw, shouldShowImeSwitcherWhenImeIsShownLocked()); } @AnyThread @@ -2731,6 +2733,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") + boolean shouldShowImeSwitcherWhenImeIsShownLocked() { + return shouldShowImeSwitcherLocked( + InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE); + } + + @GuardedBy("ImfLock.class") private boolean shouldShowImeSwitcherLocked(int visibility) { if (!mShowOngoingImeSwitcherForPhones) return false; if (mMenuController.getSwitchingDialogLocked() != null) return false; @@ -2990,6 +2998,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // the same enabled IMEs list. mSwitchingController.resetCircularListLocked(mContext); + sendShouldShowImeSwitcherWhenImeIsShownLocked(); } @GuardedBy("ImfLock.class") @@ -4308,6 +4317,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub updateImeWindowStatus(msg.arg1 == 1); return true; } + // --------------------------------------------------------- case MSG_UNBIND_CLIENT: @@ -4368,6 +4378,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // -------------------------------------------------------------- case MSG_HARD_KEYBOARD_SWITCH_CHANGED: mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1); + synchronized (ImfLock.class) { + sendShouldShowImeSwitcherWhenImeIsShownLocked(); + } return true; case MSG_SYSTEM_UNLOCK_USER: { final int userId = msg.arg1; @@ -4638,6 +4651,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // the same enabled IMEs list. mSwitchingController.resetCircularListLocked(mContext); + sendShouldShowImeSwitcherWhenImeIsShownLocked(); + // Notify InputMethodListListeners of the new installed InputMethods. final List<InputMethodInfo> inputMethodList = new ArrayList<>(mMethodList); mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED, @@ -4645,6 +4660,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") + void sendShouldShowImeSwitcherWhenImeIsShownLocked() { + final IInputMethodInvoker curMethod = mBindingController.getCurMethod(); + if (curMethod == null) { + // No need to send the data if the IME is not yet bound. + return; + } + curMethod.onShouldShowImeSwitcherWhenImeIsShownChanged( + shouldShowImeSwitcherWhenImeIsShownLocked()); + } + + @GuardedBy("ImfLock.class") private void updateDefaultVoiceImeIfNeededLocked() { final String systemSpeechRecognizer = mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 348bb2d868a2..98bde11ad517 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -203,6 +203,7 @@ final class InputMethodMenuController { attrs.setTitle("Select input method"); w.setAttributes(attrs); mService.updateSystemUiLocked(); + mService.sendShouldShowImeSwitcherWhenImeIsShownLocked(); mSwitchingDialog.show(); } } @@ -238,6 +239,7 @@ final class InputMethodMenuController { mSwitchingDialogTitleView = null; mService.updateSystemUiLocked(); + mService.sendShouldShowImeSwitcherWhenImeIsShownLocked(); mDialogBuilder = null; mIms = null; } diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index aa1fa9ba1221..0c3f9f0e26c6 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -17,6 +17,7 @@ package com.android.server.location; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import static android.app.compat.CompatChanges.isChangeEnabled; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; @@ -829,16 +830,12 @@ public class LocationManagerService extends ILocationManager.Stub implements "only verified adas packages may use adas gnss bypass requests"); } if (!isLocationProvider) { - mContext.enforceCallingOrSelfPermission( - permission.WRITE_SECURE_SETTINGS, - "adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS); + LocationPermissions.enforceCallingOrSelfBypassPermission(mContext); } } if (request.isLocationSettingsIgnored()) { if (!isLocationProvider) { - mContext.enforceCallingOrSelfPermission( - permission.WRITE_SECURE_SETTINGS, - "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS); + LocationPermissions.enforceCallingOrSelfBypassPermission(mContext); } } @@ -933,16 +930,12 @@ public class LocationManagerService extends ILocationManager.Stub implements "only verified adas packages may use adas gnss bypass requests"); } if (!isLocationProvider) { - mContext.enforceCallingOrSelfPermission( - permission.WRITE_SECURE_SETTINGS, - "adas gnss bypass requires " + permission.WRITE_SECURE_SETTINGS); + LocationPermissions.enforceCallingOrSelfBypassPermission(mContext); } } if (request.isLocationSettingsIgnored()) { if (!isLocationProvider) { - mContext.enforceCallingOrSelfPermission( - permission.WRITE_SECURE_SETTINGS, - "ignoring location settings requires " + permission.WRITE_SECURE_SETTINGS); + LocationPermissions.enforceCallingOrSelfBypassPermission(mContext); } } @@ -1202,7 +1195,7 @@ public class LocationManagerService extends ILocationManager.Stub implements userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, "setLocationEnabledForUser", null); - mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null); + mContext.enforceCallingOrSelfPermission(WRITE_SECURE_SETTINGS, null); LocationManager.invalidateLocalLocationEnabledCaches(); mInjector.getSettingsHelper().setLocationEnabled(enabled, userId); @@ -1220,7 +1213,7 @@ public class LocationManagerService extends ILocationManager.Stub implements userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, "setAdasGnssLocationEnabledForUser", null); - mContext.enforceCallingOrSelfPermission(permission.WRITE_SECURE_SETTINGS, null); + LocationPermissions.enforceCallingOrSelfBypassPermission(mContext); mInjector.getLocationSettings().updateUserSettings(userId, settings -> settings.withAdasGnssLocationEnabled(enabled)); diff --git a/services/core/java/com/android/server/location/LocationPermissions.java b/services/core/java/com/android/server/location/LocationPermissions.java index 7528f8b6ea64..be702d906d3b 100644 --- a/services/core/java/com/android/server/location/LocationPermissions.java +++ b/services/core/java/com/android/server/location/LocationPermissions.java @@ -18,6 +18,8 @@ package com.android.server.location; import static android.Manifest.permission.ACCESS_COARSE_LOCATION; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.LOCATION_BYPASS; +import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.annotation.IntDef; @@ -121,6 +123,29 @@ public final class LocationPermissions { } /** + * Throws a security exception if the caller does not hold the required bypass permissions. + */ + public static void enforceCallingOrSelfBypassPermission(Context context) { + enforceBypassPermission(context, Binder.getCallingUid(), Binder.getCallingPid()); + } + + /** + * Throws a security exception if the given uid/pid does not hold the required bypass + * perissions. + */ + public static void enforceBypassPermission(Context context, int uid, int pid) { + if (context.checkPermission(WRITE_SECURE_SETTINGS, pid, uid) == PERMISSION_GRANTED) { + // TODO: disallow WRITE_SECURE_SETTINGS permission. + return; + } + if (context.checkPermission(LOCATION_BYPASS, pid, uid) == PERMISSION_GRANTED) { + return; + } + throw new SecurityException("uid" + uid + " does not have " + LOCATION_BYPASS + + "or " + WRITE_SECURE_SETTINGS + "."); + } + + /** * Returns false if the caller does not hold the required location permissions. */ public static boolean checkCallingOrSelfLocationPermission(Context context, diff --git a/services/core/java/com/android/server/location/LocationShellCommand.java b/services/core/java/com/android/server/location/LocationShellCommand.java index b65338d9691d..9c85d18515af 100644 --- a/services/core/java/com/android/server/location/LocationShellCommand.java +++ b/services/core/java/com/android/server/location/LocationShellCommand.java @@ -69,6 +69,14 @@ class LocationShellCommand extends BasicShellCommandHandler { handleSetAdasGnssLocationEnabled(); return 0; } + case "set-automotive-gnss-suspended": { + handleSetAutomotiveGnssSuspended(); + return 0; + } + case "is-automotive-gnss-suspended": { + handleIsAutomotiveGnssSuspended(); + return 0; + } case "providers": { String command = getNextArgRequired(); return parseProvidersCommand(command); @@ -189,6 +197,24 @@ class LocationShellCommand extends BasicShellCommandHandler { mService.setAdasGnssLocationEnabledForUser(enabled, userId); } + private void handleSetAutomotiveGnssSuspended() { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { + throw new IllegalStateException("command only recognized on automotive devices"); + } + + boolean suspended = Boolean.parseBoolean(getNextArgRequired()); + + mService.setAutomotiveGnssSuspended(suspended); + } + + private void handleIsAutomotiveGnssSuspended() { + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { + throw new IllegalStateException("command only recognized on automotive devices"); + } + + getOutPrintWriter().println(mService.isAutomotiveGnssSuspended()); + } + private void handleAddTestProvider() { String provider = getNextArgRequired(); @@ -359,6 +385,10 @@ class LocationShellCommand extends BasicShellCommandHandler { pw.println(" set-adas-gnss-location-enabled true|false [--user <USER_ID>]"); pw.println(" Sets the ADAS GNSS location enabled state. If no user is specified,"); pw.println(" the current user is assumed."); + pw.println(" is-automotive-gnss-suspended"); + pw.println(" Gets the automotive GNSS suspended state."); + pw.println(" set-automotive-gnss-suspended true|false"); + pw.println(" Sets the automotive GNSS suspended state."); } pw.println(" providers"); pw.println(" The providers command is followed by a subcommand, as listed below:"); diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index 21ea1f600b71..a1ee46b4c943 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -465,7 +465,7 @@ public abstract class IContextHubWrapper { try { mHub.onHostEndpointConnected(info); } catch (RemoteException | ServiceSpecificException e) { - Log.e(TAG, "RemoteException in onHostEndpointConnected"); + Log.e(TAG, "Exception in onHostEndpointConnected" + e.getMessage()); } } @@ -474,7 +474,7 @@ public abstract class IContextHubWrapper { try { mHub.onHostEndpointDisconnected((char) hostEndpointId); } catch (RemoteException | ServiceSpecificException e) { - Log.e(TAG, "RemoteException in onHostEndpointDisconnected"); + Log.e(TAG, "Exception in onHostEndpointDisconnected" + e.getMessage()); } } @@ -488,6 +488,8 @@ public abstract class IContextHubWrapper { return ContextHubTransaction.RESULT_SUCCESS; } catch (RemoteException | ServiceSpecificException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } catch (IllegalArgumentException e) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; } } @@ -499,8 +501,10 @@ public abstract class IContextHubWrapper { try { mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId); return ContextHubTransaction.RESULT_SUCCESS; - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } catch (IllegalArgumentException e) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; } } @@ -510,8 +514,10 @@ public abstract class IContextHubWrapper { try { mHub.unloadNanoapp(contextHubId, nanoappId, transactionId); return ContextHubTransaction.RESULT_SUCCESS; - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } catch (IllegalArgumentException e) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; } } @@ -521,8 +527,10 @@ public abstract class IContextHubWrapper { try { mHub.enableNanoapp(contextHubId, nanoappId, transactionId); return ContextHubTransaction.RESULT_SUCCESS; - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } catch (IllegalArgumentException e) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; } } @@ -532,8 +540,10 @@ public abstract class IContextHubWrapper { try { mHub.disableNanoapp(contextHubId, nanoappId, transactionId); return ContextHubTransaction.RESULT_SUCCESS; - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } catch (IllegalArgumentException e) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; } } @@ -542,8 +552,10 @@ public abstract class IContextHubWrapper { try { mHub.queryNanoapps(contextHubId); return ContextHubTransaction.RESULT_SUCCESS; - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; + } catch (IllegalArgumentException e) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; } } @@ -551,7 +563,7 @@ public abstract class IContextHubWrapper { mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback)); try { mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId)); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | IllegalArgumentException e) { Log.e(TAG, "Exception while registering callback: " + e.getMessage()); } } diff --git a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java index 5036a6e7edf5..bfef97856838 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java @@ -63,16 +63,25 @@ class GnssNmeaProvider extends GnssListenerMultiplexer<Void, IGnssNmeaListener, @Override protected boolean registerWithService(Void ignored, Collection<GnssListenerRegistration> registrations) { - if (D) { - Log.d(TAG, "starting gnss nmea messages"); + if (mGnssNative.startNmeaMessageCollection()) { + if (D) { + Log.d(TAG, "starting gnss nmea messages collection"); + } + return true; + } else { + Log.e(TAG, "error starting gnss nmea messages collection"); + return false; } - return true; } @Override protected void unregisterWithService() { - if (D) { - Log.d(TAG, "stopping gnss nmea messages"); + if (mGnssNative.stopNmeaMessageCollection()) { + if (D) { + Log.d(TAG, "stopping gnss nmea messages collection"); + } + } else { + Log.e(TAG, "error stopping gnss nmea messages collection"); } } diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java index 936283deda8e..0ce36d6a8276 100644 --- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java @@ -43,6 +43,7 @@ public class GnssStatusProvider extends private final AppOpsHelper mAppOpsHelper; private final LocationUsageLogger mLogger; + private final GnssNative mGnssNative; private boolean mIsNavigating = false; @@ -50,6 +51,7 @@ public class GnssStatusProvider extends super(injector); mAppOpsHelper = injector.getAppOpsHelper(); mLogger = injector.getLocationUsageLogger(); + mGnssNative = gnssNative; gnssNative.addBaseCallbacks(this); gnssNative.addStatusCallbacks(this); @@ -64,16 +66,25 @@ public class GnssStatusProvider extends @Override protected boolean registerWithService(Void ignored, Collection<GnssListenerRegistration> registrations) { - if (D) { - Log.d(TAG, "starting gnss status"); + if (mGnssNative.startSvStatusCollection()) { + if (D) { + Log.d(TAG, "starting gnss sv status"); + } + return true; + } else { + Log.e(TAG, "error starting gnss sv status"); + return false; } - return true; } @Override protected void unregisterWithService() { - if (D) { - Log.d(TAG, "stopping gnss status"); + if (mGnssNative.stopSvStatusCollection()) { + if (D) { + Log.d(TAG, "stopping gnss sv status"); + } + } else { + Log.e(TAG, "error stopping gnss sv status"); } } diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java index e072bf7dc1f7..af87677ecb66 100644 --- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java +++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java @@ -783,6 +783,38 @@ public class GnssNative { } /** + * Starts sv status collection. + */ + public boolean startSvStatusCollection() { + Preconditions.checkState(mRegistered); + return mGnssHal.startSvStatusCollection(); + } + + /** + * Stops sv status collection. + */ + public boolean stopSvStatusCollection() { + Preconditions.checkState(mRegistered); + return mGnssHal.stopSvStatusCollection(); + } + + /** + * Starts NMEA message collection. + */ + public boolean startNmeaMessageCollection() { + Preconditions.checkState(mRegistered); + return mGnssHal.startNmeaMessageCollection(); + } + + /** + * Stops NMEA message collection. + */ + public boolean stopNmeaMessageCollection() { + Preconditions.checkState(mRegistered); + return mGnssHal.stopNmeaMessageCollection(); + } + + /** * Returns true if measurement corrections are supported. */ public boolean isMeasurementCorrectionsSupported() { @@ -1369,6 +1401,22 @@ public class GnssNative { return native_inject_measurement_corrections(corrections); } + protected boolean startSvStatusCollection() { + return native_start_sv_status_collection(); + } + + protected boolean stopSvStatusCollection() { + return native_stop_sv_status_collection(); + } + + protected boolean startNmeaMessageCollection() { + return native_start_nmea_message_collection(); + } + + protected boolean stopNmeaMessageCollection() { + return native_stop_nmea_message_collection(); + } + protected int getBatchSize() { return native_get_batch_size(); } @@ -1478,6 +1526,10 @@ public class GnssNative { private static native int native_read_nmea(byte[] buffer, int bufferSize); + private static native boolean native_start_nmea_message_collection(); + + private static native boolean native_stop_nmea_message_collection(); + // location injection APIs private static native void native_inject_location( @@ -1501,6 +1553,11 @@ public class GnssNative { private static native void native_inject_time(long time, long timeReference, int uncertainty); + // sv status APIs + private static native boolean native_start_sv_status_collection(); + + private static native boolean native_stop_sv_status_collection(); + // navigation message APIs private static native boolean native_is_navigation_message_supported(); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 8f051303bf03..2d2edfa51896 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.READ_CONTACTS; import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS; +import static android.Manifest.permission.SET_INITIAL_LOCK; import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL; import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE; @@ -1650,9 +1651,13 @@ public class LockSettingsService extends ILockSettings.Stub { "This operation requires secure lock screen feature"); } if (!hasPermission(PERMISSION) && !hasPermission(SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS)) { - throw new SecurityException( - "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or " - + PERMISSION); + if (hasPermission(SET_INITIAL_LOCK) && savedCredential.isNone()) { + // SET_INITIAL_LOCK can only be used if credential is not set. + } else { + throw new SecurityException( + "setLockCredential requires SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS or " + + PERMISSION); + } } final long identity = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java new file mode 100644 index 000000000000..6b442a6a395e --- /dev/null +++ b/services/core/java/com/android/server/logcat/LogAccessConfirmationActivity.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.logcat; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentSender; +import android.os.Bundle; +import android.os.ServiceManager; +import android.os.logcat.ILogcatManagerService; +import android.util.Slog; +import android.view.View; +import android.widget.TextView; + +import com.android.internal.R; +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + + +/** + * This dialog is shown to the user before an activity in a harmful app is launched. + * + * See {@code PackageManager.setLogcatAppInfo} for more info. + */ +public class LogAccessConfirmationActivity extends AlertActivity implements + DialogInterface.OnClickListener { + private static final String TAG = LogAccessConfirmationActivity.class.getSimpleName(); + + private String mPackageName; + private IntentSender mTarget; + private final ILogcatManagerService mLogcatManagerService = + ILogcatManagerService.Stub.asInterface(ServiceManager.getService("logcat")); + + private int mUid; + private int mGid; + private int mPid; + private int mFd; + + private static final String EXTRA_UID = "uid"; + private static final String EXTRA_GID = "gid"; + private static final String EXTRA_PID = "pid"; + private static final String EXTRA_FD = "fd"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Intent intent = getIntent(); + mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); + mUid = intent.getIntExtra("uid", 0); + mGid = intent.getIntExtra("gid", 0); + mPid = intent.getIntExtra("pid", 0); + mFd = intent.getIntExtra("fd", 0); + + final AlertController.AlertParams p = mAlertParams; + p.mTitle = getString(R.string.log_access_confirmation_title); + p.mView = createView(); + + p.mPositiveButtonText = getString(R.string.log_access_confirmation_allow); + p.mPositiveButtonListener = this; + p.mNegativeButtonText = getString(R.string.log_access_confirmation_deny); + p.mNegativeButtonListener = this; + + mAlert.installContent(mAlertParams); + } + + private View createView() { + final View view = getLayoutInflater().inflate(R.layout.harmful_app_warning_dialog, + null /*root*/); + ((TextView) view.findViewById(R.id.app_name_text)) + .setText(mPackageName); + ((TextView) view.findViewById(R.id.message)) + .setText(getIntent().getExtras().getString("body")); + return view; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + try { + mLogcatManagerService.approve(mUid, mGid, mPid, mFd); + } catch (Throwable t) { + Slog.e(TAG, "Could not start the LogcatManagerService.", t); + } + finish(); + break; + case DialogInterface.BUTTON_NEGATIVE: + try { + mLogcatManagerService.decline(mUid, mGid, mPid, mFd); + } catch (Throwable t) { + Slog.e(TAG, "Could not start the LogcatManagerService.", t); + } + finish(); + break; + } + } + + /** + * Create the Intent for a LogAccessConfirmationActivity. + */ + public static Intent createIntent(Context context, String targetPackageName, + IntentSender target, int uid, int gid, int pid, int fd) { + final Intent intent = new Intent(); + intent.setClass(context, LogAccessConfirmationActivity.class); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName); + intent.putExtra(EXTRA_UID, uid); + intent.putExtra(EXTRA_GID, gid); + intent.putExtra(EXTRA_PID, pid); + intent.putExtra(EXTRA_FD, fd); + + return intent; + } + +} diff --git a/services/core/java/com/android/server/logcat/LogcatManagerService.java b/services/core/java/com/android/server/logcat/LogcatManagerService.java index ff6372aec3bd..616ae4403f4f 100644 --- a/services/core/java/com/android/server/logcat/LogcatManagerService.java +++ b/services/core/java/com/android/server/logcat/LogcatManagerService.java @@ -16,20 +16,35 @@ package com.android.server.logcat; +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.ActivityManagerInternal; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.os.ILogd; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.os.logcat.ILogcatManagerService; import android.util.Slog; +import com.android.internal.R; +import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.util.ArrayUtils; +import com.android.server.LocalServices; import com.android.server.SystemService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** - * Service responsible for manage the access to Logcat. + * Service responsible for managing the access to Logcat. */ public final class LogcatManagerService extends SystemService { @@ -38,6 +53,12 @@ public final class LogcatManagerService extends SystemService { private final BinderService mBinderService; private final ExecutorService mThreadExecutor; private ILogd mLogdService; + private NotificationManager mNotificationManager; + private @NonNull ActivityManager mActivityManager; + private ActivityManagerInternal mActivityManagerInternal; + private static final int MAX_UID_IMPORTANCE_COUNT_LISTENER = 2; + private static int sUidImportanceListenerCount = 0; + private static final int AID_SHELL_UID = 2000; private final class BinderService extends ILogcatManagerService.Stub { @Override @@ -51,6 +72,197 @@ public final class LogcatManagerService extends SystemService { // the logd data access is finished. mThreadExecutor.execute(new LogdMonitor(uid, gid, pid, fd, false)); } + + @Override + public void approve(int uid, int gid, int pid, int fd) { + try { + getLogdService().approve(uid, gid, pid, fd); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + public void decline(int uid, int gid, int pid, int fd) { + try { + getLogdService().decline(uid, gid, pid, fd); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + private ILogd getLogdService() { + synchronized (LogcatManagerService.this) { + if (mLogdService == null) { + LogcatManagerService.this.addLogdService(); + } + return mLogdService; + } + } + + private String getBodyString(Context context, String callingPackage, int uid) { + PackageManager pm = context.getPackageManager(); + try { + return context.getString( + com.android.internal.R.string.log_access_confirmation_body, + pm.getApplicationInfoAsUser(callingPackage, PackageManager.MATCH_DIRECT_BOOT_AUTO, + UserHandle.getUserId(uid)).loadLabel(pm)); + } catch (NameNotFoundException e) { + // App name is unknown. + return null; + } + } + + private void sendNotification(int notificationId, String clientInfo, int uid, int gid, int pid, + int fd) { + + final ActivityManagerInternal activityManagerInternal = + LocalServices.getService(ActivityManagerInternal.class); + + PackageManager pm = mContext.getPackageManager(); + String packageName = activityManagerInternal.getPackageNameByPid(pid); + if (packageName != null) { + String notificationBody = getBodyString(mContext, packageName, uid); + + final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext, + packageName, null, uid, gid, pid, fd); + + if (notificationBody == null) { + // Decline the logd access if the nofitication body is unknown + Slog.e(TAG, "Unknown notification body, declining the logd access"); + declineLogdAccess(uid, gid, pid, fd); + return; + } + + // TODO Next version will replace notification with dialogue + // per UX guidance. + generateNotificationWithBodyContent(notificationId, clientInfo, notificationBody, + mIntent); + return; + + } + + String[] packageNames = pm.getPackagesForUid(uid); + + if (ArrayUtils.isEmpty(packageNames)) { + // Decline the logd access if the app name is unknown + Slog.e(TAG, "Unknown calling package name, declining the logd access"); + declineLogdAccess(uid, gid, pid, fd); + return; + } + + String firstPackageName = packageNames[0]; + + if (firstPackageName == null || firstPackageName.length() == 0) { + // Decline the logd access if the package name from uid is unknown + Slog.e(TAG, "Unknown calling package name, declining the logd access"); + declineLogdAccess(uid, gid, pid, fd); + return; + } + + String notificationBody = getBodyString(mContext, firstPackageName, uid); + + final Intent mIntent = LogAccessConfirmationActivity.createIntent(mContext, + firstPackageName, null, uid, gid, pid, fd); + + if (notificationBody == null) { + Slog.e(TAG, "Unknown notification body, declining the logd access"); + declineLogdAccess(uid, gid, pid, fd); + return; + } + + // TODO Next version will replace notification with dialogue + // per UX guidance. + generateNotificationWithBodyContent(notificationId, clientInfo, + notificationBody, mIntent); + } + + private void declineLogdAccess(int uid, int gid, int pid, int fd) { + try { + getLogdService().decline(uid, gid, pid, fd); + } catch (RemoteException ex) { + Slog.e(TAG, "Fails to call remote functions ", ex); + } + } + + private void generateNotificationWithBodyContent(int notificationId, String clientInfo, + String notificationBody, Intent intent) { + final Notification.Builder notificationBuilder = new Notification.Builder( + mContext, + SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY); + intent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.setIdentifier(String.valueOf(notificationId) + clientInfo); + intent.putExtra("body", notificationBody); + + notificationBuilder + .setSmallIcon(R.drawable.ic_info) + .setContentTitle( + mContext.getString(R.string.log_access_confirmation_title)) + .setContentText(notificationBody) + .setContentIntent( + PendingIntent.getActivity(mContext, 0, intent, + PendingIntent.FLAG_IMMUTABLE)) + .setTicker(mContext.getString(R.string.log_access_confirmation_title)) + .setOnlyAlertOnce(true) + .setAutoCancel(true); + mNotificationManager.notify(notificationId, notificationBuilder.build()); + } + + /** + * A class which watches an uid for background access and notifies the logdMonitor when + * the package status becomes foreground (importance change) + */ + private class UidImportanceListener implements ActivityManager.OnUidImportanceListener { + private final int mExpectedUid; + private final int mExpectedGid; + private final int mExpectedPid; + private final int mExpectedFd; + private int mExpectedImportance; + private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE; + + UidImportanceListener(int uid, int gid, int pid, int fd, int importance) { + mExpectedUid = uid; + mExpectedGid = gid; + mExpectedPid = pid; + mExpectedFd = fd; + mExpectedImportance = importance; + } + + @Override + public void onUidImportance(int uid, int importance) { + if (uid == mExpectedUid) { + mCurrentImportance = importance; + + /** + * 1) If the process status changes to foreground, send a notification + * for user consent. + * 2) If the process status remains background, we decline logd access request. + **/ + if (importance <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) { + String clientInfo = getClientInfo(uid, mExpectedGid, mExpectedPid, mExpectedFd); + sendNotification(0, clientInfo, uid, mExpectedGid, mExpectedPid, + mExpectedFd); + mActivityManager.removeOnUidImportanceListener(this); + + synchronized (LogcatManagerService.this) { + sUidImportanceListenerCount--; + } + } else { + try { + getLogdService().decline(uid, mExpectedGid, mExpectedPid, mExpectedFd); + } catch (RemoteException ex) { + Slog.e(TAG, "Fails to call remote functions ", ex); + } + } + } + } + } + + private static String getClientInfo(int uid, int gid, int pid, int fd) { + return "UID=" + Integer.toString(uid) + " GID=" + Integer.toString(gid) + " PID=" + + Integer.toString(pid) + " FD=" + Integer.toString(fd); } private class LogdMonitor implements Runnable { @@ -74,9 +286,7 @@ public final class LogcatManagerService extends SystemService { } /** - * The current version grant the permission by default. - * And track the logd access. - * The next version will generate a prompt for users. + * LogdMonitor generates a prompt for users. * The users decide whether the logd access is allowed. */ @Override @@ -86,10 +296,51 @@ public final class LogcatManagerService extends SystemService { } if (mStart) { - try { - mLogdService.approve(mUid, mGid, mPid, mFd); - } catch (RemoteException ex) { - Slog.e(TAG, "Fails to call remote functions ", ex); + + // If the access request is coming from adb shell, approve the logd access + if (mUid == AID_SHELL_UID) { + try { + getLogdService().approve(mUid, mGid, mPid, mFd); + } catch (RemoteException e) { + e.printStackTrace(); + } + return; + } + + final int procState = LocalServices.getService(ActivityManagerInternal.class) + .getUidProcessState(mUid); + // If the process is foreground, send a notification for user consent + if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { + String clientInfo = getClientInfo(mUid, mGid, mPid, mFd); + sendNotification(0, clientInfo, mUid, mGid, mPid, mFd); + } else { + /** + * If the process is background, add a background process change listener and + * monitor if the process status changes. + * To avoid clients registering multiple listeners, we limit the number of + * maximum listeners to MAX_UID_IMPORTANCE_COUNT_LISTENER. + **/ + if (mActivityManager == null) { + return; + } + + synchronized (LogcatManagerService.this) { + if (sUidImportanceListenerCount < MAX_UID_IMPORTANCE_COUNT_LISTENER) { + // Trigger addOnUidImportanceListener when there is an update from + // the importance of the process + mActivityManager.addOnUidImportanceListener(new UidImportanceListener( + mUid, mGid, mPid, mFd, + RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE), + RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE); + sUidImportanceListenerCount++; + } else { + try { + getLogdService().decline(mUid, mGid, mPid, mFd); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } } } } @@ -100,6 +351,8 @@ public final class LogcatManagerService extends SystemService { mContext = context; mBinderService = new BinderService(); mThreadExecutor = Executors.newCachedThreadPool(); + mActivityManager = context.getSystemService(ActivityManager.class); + mNotificationManager = mContext.getSystemService(NotificationManager.class); } @Override @@ -114,5 +367,4 @@ public final class LogcatManagerService extends SystemService { private void addLogdService() { mLogdService = ILogd.Stub.asInterface(ServiceManager.getService("logd")); } - } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 303ab4669855..7f997df3b222 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -677,9 +677,9 @@ class MediaRouter2ServiceImpl { UserRecord userRecord = routerRecord.mUserRecord; userRecord.mRouterRecords.remove(routerRecord); routerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers, + obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers, routerRecord.mUserRecord.mHandler, - routerRecord.mPackageName, /* preferredFeatures=*/ null)); + routerRecord.mPackageName, null)); userRecord.mHandler.sendMessage( obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler, userRecord.mHandler)); @@ -694,10 +694,10 @@ class MediaRouter2ServiceImpl { } routerRecord.mDiscoveryPreference = discoveryRequest; routerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers, + obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers, routerRecord.mUserRecord.mHandler, routerRecord.mPackageName, - routerRecord.mDiscoveryPreference.getPreferredFeatures())); + routerRecord.mDiscoveryPreference)); routerRecord.mUserRecord.mHandler.sendMessage( obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler, routerRecord.mUserRecord.mHandler)); @@ -921,7 +921,7 @@ class MediaRouter2ServiceImpl { // TODO: UserRecord <-> routerRecord, why do they reference each other? // How about removing mUserRecord from routerRecord? routerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManager, + obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManager, routerRecord.mUserRecord.mHandler, routerRecord, manager)); } @@ -2118,19 +2118,19 @@ class MediaRouter2ServiceImpl { } } - private void notifyPreferredFeaturesChangedToManager(@NonNull RouterRecord routerRecord, + private void notifyDiscoveryPreferenceChangedToManager(@NonNull RouterRecord routerRecord, @NonNull IMediaRouter2Manager manager) { try { - manager.notifyPreferredFeaturesChanged(routerRecord.mPackageName, - routerRecord.mDiscoveryPreference.getPreferredFeatures()); + manager.notifyDiscoveryPreferenceChanged(routerRecord.mPackageName, + routerRecord.mDiscoveryPreference); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify preferred features changed." + " Manager probably died.", ex); } } - private void notifyPreferredFeaturesChangedToManagers(@NonNull String routerPackageName, - @Nullable List<String> preferredFeatures) { + private void notifyDiscoveryPreferenceChangedToManagers(@NonNull String routerPackageName, + @Nullable RouteDiscoveryPreference discoveryPreference) { MediaRouter2ServiceImpl service = mServiceRef.get(); if (service == null) { return; @@ -2143,7 +2143,8 @@ class MediaRouter2ServiceImpl { } for (IMediaRouter2Manager manager : managers) { try { - manager.notifyPreferredFeaturesChanged(routerPackageName, preferredFeatures); + manager.notifyDiscoveryPreferenceChanged(routerPackageName, + discoveryPreference); } catch (RemoteException ex) { Slog.w(TAG, "Failed to notify preferred features changed." + " Manager probably died.", ex); diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 583cdd599780..647a89efcbbf 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -104,6 +104,12 @@ public class NotificationComparator return -1 * Boolean.compare(leftPeople, rightPeople); } + boolean leftSystemMax = isSystemMax(left); + boolean rightSystemMax = isSystemMax(right); + if (leftSystemMax != rightSystemMax) { + return -1 * Boolean.compare(leftSystemMax, rightSystemMax); + } + if (leftImportance != rightImportance) { // by importance, high to low return -1 * Integer.compare(leftImportance, rightImportance); @@ -173,6 +179,20 @@ public class NotificationComparator return mMessagingUtil.isImportantMessaging(record.getSbn(), record.getImportance()); } + protected boolean isSystemMax(NotificationRecord record) { + if (record.getImportance() < NotificationManager.IMPORTANCE_HIGH) { + return false; + } + String packageName = record.getSbn().getPackageName(); + if ("android".equals(packageName)) { + return true; + } + if ("com.android.systemui".equals(packageName)) { + return true; + } + return false; + } + private boolean isOngoing(NotificationRecord record) { final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE; return (record.getNotification().flags & ongoingFlags) != 0; diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java index 0cbdbc18ad39..5d18069ea205 100644 --- a/services/core/java/com/android/server/notification/PermissionHelper.java +++ b/services/core/java/com/android/server/notification/PermissionHelper.java @@ -19,7 +19,7 @@ package com.android.server.notification; import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; import static android.content.pm.PackageManager.GET_PERMISSIONS; -import static android.permission.PermissionManager.PERMISSION_GRANTED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; import android.annotation.NonNull; @@ -77,7 +77,8 @@ public final class PermissionHelper { assertFlag(); final long callingId = Binder.clearCallingIdentity(); try { - return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED; + return mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(uid) + == PERMISSION_GRANTED; } finally { Binder.restoreCallingIdentity(callingId); } diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java index b186f610d498..29aad63a1f4b 100644 --- a/services/core/java/com/android/server/notification/ZenModeFiltering.java +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -24,11 +24,14 @@ import android.app.NotificationManager; import android.content.ComponentName; import android.content.Context; import android.media.AudioAttributes; +import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings.Global; import android.service.notification.ZenModeConfig; import android.telecom.TelecomManager; +import android.telephony.PhoneNumberUtils; +import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Slog; @@ -36,6 +39,8 @@ import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.util.NotificationMessagingUtil; import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.util.Date; public class ZenModeFiltering { @@ -64,13 +69,22 @@ public class ZenModeFiltering { pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes="); pw.println(REPEAT_CALLERS.mThresholdMinutes); synchronized (REPEAT_CALLERS) { - if (!REPEAT_CALLERS.mCalls.isEmpty()) { - pw.print(prefix); pw.println("RepeatCallers.mCalls="); - for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) { + if (!REPEAT_CALLERS.mTelCalls.isEmpty()) { + pw.print(prefix); pw.println("RepeatCallers.mTelCalls="); + for (int i = 0; i < REPEAT_CALLERS.mTelCalls.size(); i++) { pw.print(prefix); pw.print(" "); - pw.print(REPEAT_CALLERS.mCalls.keyAt(i)); + pw.print(REPEAT_CALLERS.mTelCalls.keyAt(i)); pw.print(" at "); - pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i))); + pw.println(ts(REPEAT_CALLERS.mTelCalls.valueAt(i))); + } + } + if (!REPEAT_CALLERS.mOtherCalls.isEmpty()) { + pw.print(prefix); pw.println("RepeatCallers.mOtherCalls="); + for (int i = 0; i < REPEAT_CALLERS.mOtherCalls.size(); i++) { + pw.print(prefix); pw.print(" "); + pw.print(REPEAT_CALLERS.mOtherCalls.keyAt(i)); + pw.print(" at "); + pw.println(ts(REPEAT_CALLERS.mOtherCalls.valueAt(i))); } } } @@ -330,34 +344,39 @@ public class ZenModeFiltering { } private static class RepeatCallers { - // Person : time - private final ArrayMap<String, Long> mCalls = new ArrayMap<>(); + // We keep a separate map per uri scheme to do more generous number-matching + // handling on telephone numbers specifically. For other inputs, we + // simply match directly on the string. + private final ArrayMap<String, Long> mTelCalls = new ArrayMap<>(); + private final ArrayMap<String, Long> mOtherCalls = new ArrayMap<>(); private int mThresholdMinutes; private synchronized void recordCall(Context context, Bundle extras) { setThresholdMinutes(context); if (mThresholdMinutes <= 0 || extras == null) return; - final String peopleString = peopleString(extras); - if (peopleString == null) return; + final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras); + if (extraPeople == null || extraPeople.length == 0) return; final long now = System.currentTimeMillis(); - cleanUp(mCalls, now); - mCalls.put(peopleString, now); + cleanUp(mTelCalls, now); + cleanUp(mOtherCalls, now); + recordCallers(extraPeople, now); } private synchronized boolean isRepeat(Context context, Bundle extras) { setThresholdMinutes(context); if (mThresholdMinutes <= 0 || extras == null) return false; - final String peopleString = peopleString(extras); - if (peopleString == null) return false; + final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras); + if (extraPeople == null || extraPeople.length == 0) return false; final long now = System.currentTimeMillis(); - cleanUp(mCalls, now); - return mCalls.containsKey(peopleString); + cleanUp(mTelCalls, now); + cleanUp(mOtherCalls, now); + return checkCallers(context, extraPeople); } private synchronized void cleanUp(ArrayMap<String, Long> calls, long now) { final int N = calls.size(); for (int i = N - 1; i >= 0; i--) { - final long time = mCalls.valueAt(i); + final long time = calls.valueAt(i); if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) { calls.removeAt(i); } @@ -367,10 +386,16 @@ public class ZenModeFiltering { // Clean up all calls that occurred after the given time. // Used only for tests, to clean up after testing. private synchronized void cleanUpCallsAfter(long timeThreshold) { - for (int i = mCalls.size() - 1; i >= 0; i--) { - final long time = mCalls.valueAt(i); + for (int i = mTelCalls.size() - 1; i >= 0; i--) { + final long time = mTelCalls.valueAt(i); if (time > timeThreshold) { - mCalls.removeAt(i); + mTelCalls.removeAt(i); + } + } + for (int j = mOtherCalls.size() - 1; j >= 0; j--) { + final long time = mOtherCalls.valueAt(j); + if (time > timeThreshold) { + mOtherCalls.removeAt(j); } } } @@ -382,21 +407,65 @@ public class ZenModeFiltering { } } - private static String peopleString(Bundle extras) { - final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras); - if (extraPeople == null || extraPeople.length == 0) return null; - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < extraPeople.length; i++) { - String extraPerson = extraPeople[i]; - if (extraPerson == null) continue; - extraPerson = extraPerson.trim(); - if (extraPerson.isEmpty()) continue; - if (sb.length() > 0) { - sb.append('|'); + private synchronized void recordCallers(String[] people, long now) { + for (int i = 0; i < people.length; i++) { + String person = people[i]; + if (person == null) continue; + final Uri uri = Uri.parse(person); + if ("tel".equals(uri.getScheme())) { + String tel = uri.getSchemeSpecificPart(); + // while ideally we should not need to do this, sometimes we have seen tel + // numbers given in a url-encoded format + try { + tel = URLDecoder.decode(tel, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // ignore, keep the original tel string + Slog.w(TAG, "unsupported encoding in tel: uri input"); + } + mTelCalls.put(tel, now); + } else { + // for non-tel calls, store the entire string, uri-component and all + mOtherCalls.put(person, now); } - sb.append(extraPerson); } - return sb.length() == 0 ? null : sb.toString(); + } + + private synchronized boolean checkCallers(Context context, String[] people) { + // get the default country code for checking telephone numbers + final String defaultCountryCode = + context.getSystemService(TelephonyManager.class).getNetworkCountryIso(); + for (int i = 0; i < people.length; i++) { + String person = people[i]; + if (person == null) continue; + final Uri uri = Uri.parse(person); + if ("tel".equals(uri.getScheme())) { + String number = uri.getSchemeSpecificPart(); + if (mTelCalls.containsKey(number)) { + // check directly via map first + return true; + } else { + // see if a number that matches via areSameNumber exists + String numberToCheck = number; + try { + numberToCheck = URLDecoder.decode(number, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // ignore, continue to use the original string + Slog.w(TAG, "unsupported encoding in tel: uri input"); + } + for (String prev : mTelCalls.keySet()) { + if (PhoneNumberUtils.areSamePhoneNumber( + numberToCheck, prev, defaultCountryCode)) { + return true; + } + } + } + } else { + if (mOtherCalls.containsKey(person)) { + return true; + } + } + } + return false; } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 50c26f4439a0..336da2acca67 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -141,7 +141,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.F2fsUtils; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import com.android.internal.security.VerityUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; @@ -2358,26 +2358,26 @@ final class InstallPackageHelper { if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { // Check for updated system application. if (installedPkg.isSystem()) { - return PackageHelper.RECOMMEND_INSTALL_INTERNAL; + return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL; } else { // If current upgrade specifies particular preference if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { // Application explicitly specified internal. - return PackageHelper.RECOMMEND_INSTALL_INTERNAL; + return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL; } else if ( installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { // App explicitly prefers external. Let policy decide } else { // Prefer previous location if (installedPkg.isExternalStorage()) { - return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; + return InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL; } - return PackageHelper.RECOMMEND_INSTALL_INTERNAL; + return InstallLocationUtils.RECOMMEND_INSTALL_INTERNAL; } } } else { // Invalid install. Return error code - return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS; + return InstallLocationUtils.RECOMMEND_FAILED_ALREADY_EXISTS; } } } diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java index d996fe46a4f6..7e845c74617b 100644 --- a/services/core/java/com/android/server/pm/InstallParams.java +++ b/services/core/java/com/android/server/pm/InstallParams.java @@ -35,18 +35,17 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.SigningDetails; import android.content.pm.parsing.PackageLite; -import android.os.Environment; import android.os.Message; import android.os.Trace; import android.os.UserHandle; -import android.os.storage.StorageManager; import android.util.ArrayMap; import android.util.Pair; import android.util.Slog; import com.android.internal.content.F2fsUtils; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import com.android.internal.util.Preconditions; +import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.File; import java.util.ArrayList; @@ -142,12 +141,8 @@ final class InstallParams extends HandlerParams { * Only {@link PackageManager#INSTALL_INTERNAL} flag may mutate in * {@link #mInstallFlags} */ - private int overrideInstallLocation(PackageInfoLite pkgLite) { - final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0; - if (DEBUG_INSTANT && ephemeral) { - Slog.v(TAG, "pkgLite for install: " + pkgLite); - } - + private int overrideInstallLocation(String packageName, int recommendedInstallLocation, + int installLocation) { if (mOriginInfo.mStaged) { // If we're already staged, we've firmly committed to an install location if (mOriginInfo.mFile != null) { @@ -155,77 +150,35 @@ final class InstallParams extends HandlerParams { } else { throw new IllegalStateException("Invalid stage location"); } - } else if (pkgLite.recommendedInstallLocation - == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) { - /* - * If we are not staged and have too little free space, try to free cache - * before giving up. - */ - // TODO: focus freeing disk space on the target device - final StorageManager storage = StorageManager.from(mPm.mContext); - final long lowThreshold = storage.getStorageLowBytes( - Environment.getDataDirectory()); - - final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize( - mOriginInfo.mResolvedPath, mPackageAbiOverride); - if (sizeBytes >= 0) { - synchronized (mPm.mInstallLock) { - try { - mPm.mInstaller.freeCache(null, sizeBytes + lowThreshold, 0); - pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext, - mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags, - mPackageAbiOverride); - } catch (Installer.InstallerException e) { - Slog.w(TAG, "Failed to free cache", e); - } - } - } - - /* - * The cache free must have deleted the file we downloaded to install. - * - * TODO: fix the "freeCache" call to not delete the file we care about. - */ - if (pkgLite.recommendedInstallLocation - == PackageHelper.RECOMMEND_FAILED_INVALID_URI) { - pkgLite.recommendedInstallLocation = - PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; + } + if (recommendedInstallLocation < 0) { + return InstallLocationUtils.getInstallationErrorCode(recommendedInstallLocation); + } + // Override with defaults if needed. + synchronized (mPm.mLock) { + // reader + AndroidPackage installedPkg = mPm.mPackages.get(packageName); + if (installedPkg != null) { + // Currently installed package which the new package is attempting to replace + recommendedInstallLocation = InstallLocationUtils.installLocationPolicy( + installLocation, recommendedInstallLocation, mInstallFlags, + installedPkg.isSystem(), installedPkg.isExternalStorage()); } } - int ret = INSTALL_SUCCEEDED; - int loc = pkgLite.recommendedInstallLocation; - if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) { - ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; - } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) { - ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS; - } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) { - ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; - } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) { - ret = PackageManager.INSTALL_FAILED_INVALID_APK; - } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) { - ret = PackageManager.INSTALL_FAILED_INVALID_URI; - } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) { - ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE; - } else { - // Override with defaults if needed. - loc = mInstallPackageHelper.installLocationPolicy(pkgLite, mInstallFlags); - - final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0; - - if (!onInt) { - // Override install location with flags - if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { - // Set the flag to install on external media. - mInstallFlags &= ~PackageManager.INSTALL_INTERNAL; - } else { - // Make sure the flag for installing on external - // media is unset - mInstallFlags |= PackageManager.INSTALL_INTERNAL; - } + final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0; + + if (!onInt) { + // Override install location with flags + if (recommendedInstallLocation == InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL) { + // Set the flag to install on external media. + mInstallFlags &= ~PackageManager.INSTALL_INTERNAL; + } else { + // Make sure the flag for installing on external media is unset + mInstallFlags |= PackageManager.INSTALL_INTERNAL; } } - return ret; + return INSTALL_SUCCEEDED; } /* @@ -254,7 +207,21 @@ final class InstallParams extends HandlerParams { } } - mRet = overrideInstallLocation(pkgLite); + final boolean ephemeral = (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0; + if (DEBUG_INSTANT && ephemeral) { + Slog.v(TAG, "pkgLite for install: " + pkgLite); + } + + if (!mOriginInfo.mStaged && pkgLite.recommendedInstallLocation + == InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) { + // If we are not staged and have too little free space, try to free cache + // before giving up. + pkgLite.recommendedInstallLocation = mPm.freeCacheForInstallation( + pkgLite.recommendedInstallLocation, mPackageLite, + mOriginInfo.mResolvedPath, mPackageAbiOverride, mInstallFlags); + } + mRet = overrideInstallLocation(pkgLite.packageName, pkgLite.recommendedInstallLocation, + pkgLite.installLocation); } @Override diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index ccc375ff85f2..8465248a6e46 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -84,7 +84,7 @@ import android.util.Xml; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.ImageUtils; @@ -782,7 +782,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements // If caller requested explicit location, validity check it, otherwise // resolve the best internal or adopted location. if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { - if (!PackageHelper.fitsOnInternal(mContext, params)) { + if (!InstallLocationUtils.fitsOnInternal(mContext, params)) { throw new IOException("No suitable internal storage available"); } } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) { @@ -796,7 +796,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements // requested install flags, delta size, and manifest settings. final long ident = Binder.clearCallingIdentity(); try { - params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params); + params.volumeUuid = InstallLocationUtils.resolveInstallVolume(mContext, params); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 390dd3fb4fc8..7152783e3d64 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -143,8 +143,8 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.NativeLibraryHelper; -import com.android.internal.content.PackageHelper; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.os.SomeArgs; import com.android.internal.security.VerityUtils; @@ -1537,7 +1537,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (stageDir != null && lengthBytes > 0) { mContext.getSystemService(StorageManager.class).allocateBytes( targetPfd.getFileDescriptor(), lengthBytes, - PackageHelper.translateAllocateFlags(params.installFlags)); + InstallLocationUtils.translateAllocateFlags(params.installFlags)); } if (offsetBytes > 0) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 79cfa06859d0..3549c9ec2e74 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -101,6 +101,7 @@ import android.content.pm.KeySet; import android.content.pm.ModuleInfo; import android.content.pm.PackageChangeEvent; import android.content.pm.PackageInfo; +import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.ComponentEnabledSetting; @@ -129,6 +130,7 @@ import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; import android.content.pm.dex.IArtManager; import android.content.pm.overlay.OverlayPaths; +import android.content.pm.parsing.PackageLite; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; @@ -189,7 +191,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ResolverActivity; import com.android.internal.content.F2fsUtils; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.om.OverlayConfig; import com.android.internal.telephony.CarrierAppUtils; import com.android.internal.util.ArrayUtils; @@ -2901,6 +2903,36 @@ public class PackageManagerService extends IPackageManager.Stub throw new IOException("Failed to free " + bytes + " on storage device at " + file); } + int freeCacheForInstallation(int recommendedInstallLocation, PackageLite pkgLite, + String resolvedPath, String mPackageAbiOverride, int installFlags) { + // TODO: focus freeing disk space on the target device + final StorageManager storage = StorageManager.from(mContext); + final long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory()); + + final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(resolvedPath, + mPackageAbiOverride); + if (sizeBytes >= 0) { + synchronized (mInstallLock) { + try { + mInstaller.freeCache(null, sizeBytes + lowThreshold, 0); + PackageInfoLite pkgInfoLite = PackageManagerServiceUtils.getMinimalPackageInfo( + mContext, pkgLite, resolvedPath, installFlags, + mPackageAbiOverride); + // The cache free must have deleted the file we downloaded to install. + if (pkgInfoLite.recommendedInstallLocation + == InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI) { + pkgInfoLite.recommendedInstallLocation = + InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; + } + return pkgInfoLite.recommendedInstallLocation; + } catch (Installer.InstallerException e) { + Slog.w(TAG, "Failed to free cache", e); + } + } + } + return recommendedInstallLocation; + } + /** * Update given flags when being used to request {@link PackageInfo}. */ @@ -3676,7 +3708,7 @@ public class PackageManagerService extends IPackageManager.Stub // Before everything else, see whether we need to fstrim. try { - IStorageManager sm = PackageHelper.getStorageManager(); + IStorageManager sm = InstallLocationUtils.getStorageManager(); if (sm != null) { boolean doTrim = false; final long interval = android.provider.Settings.Global.getLong( @@ -6595,8 +6627,9 @@ public class PackageManagerService extends IPackageManager.Stub if (getInstallLocation() == loc) { return true; } - if (loc == PackageHelper.APP_INSTALL_AUTO || loc == PackageHelper.APP_INSTALL_INTERNAL - || loc == PackageHelper.APP_INSTALL_EXTERNAL) { + if (loc == InstallLocationUtils.APP_INSTALL_AUTO + || loc == InstallLocationUtils.APP_INSTALL_INTERNAL + || loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) { android.provider.Settings.Global.putInt(mContext.getContentResolver(), android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, loc); return true; @@ -6609,7 +6642,7 @@ public class PackageManagerService extends IPackageManager.Stub // allow instant app access return android.provider.Settings.Global.getInt(mContext.getContentResolver(), android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, - PackageHelper.APP_INSTALL_AUTO); + InstallLocationUtils.APP_INSTALL_AUTO); } /** Called by UserManagerService */ diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 19c31e0b1625..d6340b5bc811 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -43,6 +43,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.PackageInfoLite; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackagePartitions; import android.content.pm.ResolveInfo; @@ -79,8 +80,8 @@ import android.util.Printer; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.NativeLibraryHelper; -import com.android.internal.content.PackageHelper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.HexDump; @@ -805,27 +806,37 @@ public class PackageManagerServiceUtils { final PackageInfoLite ret = new PackageInfoLite(); if (packagePath == null || pkg == null) { Slog.i(TAG, "Invalid package file " + packagePath); - ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; + ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK; return ret; } final File packageFile = new File(packagePath); final long sizeBytes; try { - sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride); + sizeBytes = InstallLocationUtils.calculateInstalledSize(pkg, abiOverride); } catch (IOException e) { if (!packageFile.exists()) { - ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; + ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI; } else { - ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; + ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK; } return ret; } - final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, - pkg.getPackageName(), pkg.getInstallLocation(), sizeBytes, flags); - + final PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_INVALID); + sessionParams.appPackageName = pkg.getPackageName(); + sessionParams.installLocation = pkg.getInstallLocation(); + sessionParams.sizeBytes = sizeBytes; + sessionParams.installFlags = flags; + final int recommendedInstallLocation; + try { + recommendedInstallLocation = InstallLocationUtils.resolveInstallLocation(context, + sessionParams); + } catch (IOException e) { + throw new IllegalStateException(e); + } ret.packageName = pkg.getPackageName(); ret.splitNames = pkg.getSplitNames(); ret.versionCode = pkg.getVersionCode(); @@ -837,7 +848,6 @@ public class PackageManagerServiceUtils { ret.recommendedInstallLocation = recommendedInstallLocation; ret.multiArch = pkg.isMultiArch(); ret.debuggable = pkg.isDebuggable(); - return ret; } @@ -857,7 +867,7 @@ public class PackageManagerServiceUtils { throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(), result.getException()); } - return PackageHelper.calculateInstalledSize(result.getResult(), abiOverride); + return InstallLocationUtils.calculateInstalledSize(result.getResult(), abiOverride); } catch (PackageManagerException | IOException e) { Slog.w(TAG, "Failed to calculate installed size: " + e); return -1; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 99f70b206b65..d4fcd06a6548 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -98,7 +98,7 @@ import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; @@ -593,7 +593,7 @@ class PackageManagerShellCommand extends ShellCommand { null /* splitApkPaths */, null /* splitRevisionCodes */, apkLite.getTargetSdkVersion(), null /* requiredSplitTypes */, null /* splitTypes */); - sessionSize += PackageHelper.calculateInstalledSize(pkgLite, + sessionSize += InstallLocationUtils.calculateInstalledSize(pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor()); } catch (IOException e) { getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath); @@ -922,7 +922,7 @@ class PackageManagerShellCommand extends ShellCommand { final List<SharedLibraryInfo> libs = libsSlice.getList(); for (int l = 0, lsize = libs.size(); l < lsize; ++l) { SharedLibraryInfo lib = libs.get(l); - if (lib.getType() == SharedLibraryInfo.TYPE_SDK) { + if (lib.getType() == SharedLibraryInfo.TYPE_SDK_PACKAGE) { name = lib.getName() + ":" + lib.getLongVersion(); break; } @@ -1649,11 +1649,11 @@ class PackageManagerShellCommand extends ShellCommand { private int runGetInstallLocation() throws RemoteException { int loc = mInterface.getInstallLocation(); String locStr = "invalid"; - if (loc == PackageHelper.APP_INSTALL_AUTO) { + if (loc == InstallLocationUtils.APP_INSTALL_AUTO) { locStr = "auto"; - } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) { + } else if (loc == InstallLocationUtils.APP_INSTALL_INTERNAL) { locStr = "internal"; - } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) { + } else if (loc == InstallLocationUtils.APP_INSTALL_EXTERNAL) { locStr = "external"; } getOutPrintWriter().println(loc + "[" + locStr + "]"); diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java index 9bfb7d19eee1..9db215e9093c 100644 --- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java +++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java @@ -28,7 +28,6 @@ import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; -import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.content.rollback.RollbackInfo; @@ -43,11 +42,12 @@ import android.util.Slog; import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.ParsedPackage; +import com.android.server.pm.pkg.parsing.PackageInfoWithoutStateUtils; import com.android.server.rollback.RollbackManagerInternal; import java.io.File; @@ -291,8 +291,8 @@ final class PackageSessionVerifier { throws PackageManagerException { // Before marking the session as ready, start checkpoint service if available try { - if (PackageHelper.getStorageManager().supportsCheckpoint()) { - PackageHelper.getStorageManager().startCheckpoint(2); + if (InstallLocationUtils.getStorageManager().supportsCheckpoint()) { + InstallLocationUtils.getStorageManager().startCheckpoint(2); } } catch (Exception e) { // Failed to get hold of StorageManager @@ -544,7 +544,7 @@ final class PackageSessionVerifier { */ private void checkActiveSessions() throws PackageManagerException { try { - checkActiveSessions(PackageHelper.getStorageManager().supportsCheckpoint()); + checkActiveSessions(InstallLocationUtils.getStorageManager().supportsCheckpoint()); } catch (RemoteException e) { throw new PackageManagerException(SessionInfo.SESSION_VERIFICATION_FAILED, "Can't query fs-checkpoint status : " + e); diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 29de5551cb27..f63f8f4289ed 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -53,7 +53,7 @@ import android.util.TimingsTraceLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import com.android.internal.os.BackgroundThread; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; @@ -237,7 +237,7 @@ public class StagingManager { mApexManager.revertActiveSessions(); } - PackageHelper.getStorageManager().abortChanges( + InstallLocationUtils.getStorageManager().abortChanges( "abort-staged-install", false /*retry*/); } } catch (Exception e) { @@ -674,8 +674,8 @@ public class StagingManager { boolean needsCheckpoint = false; boolean supportsCheckpoint = false; try { - supportsCheckpoint = PackageHelper.getStorageManager().supportsCheckpoint(); - needsCheckpoint = PackageHelper.getStorageManager().needsCheckpoint(); + supportsCheckpoint = InstallLocationUtils.getStorageManager().supportsCheckpoint(); + needsCheckpoint = InstallLocationUtils.getStorageManager().needsCheckpoint(); } catch (RemoteException e) { // This means that vold has crashed, and device is in a bad state. throw new IllegalStateException("Failed to get checkpoint status", e); diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 1fa901352c3d..9bcb7242b645 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -285,6 +285,19 @@ public class UserRestrictionsUtils { ); /** + * User restrictions available to a device owner whose type is + * {@link android.app.admin.DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}. + */ + private static final Set<String> FINANCED_DEVICE_OWNER_RESTRICTIONS = Sets.newArraySet( + UserManager.DISALLOW_ADD_USER, + UserManager.DISALLOW_DEBUGGING_FEATURES, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + UserManager.DISALLOW_SAFE_BOOT, + UserManager.DISALLOW_CONFIG_DATE_TIME, + UserManager.DISALLOW_OUTGOING_CALLS + ); + + /** * Returns whether the given restriction name is valid (and logs it if it isn't). */ public static boolean isValidRestriction(@NonNull String restriction) { @@ -458,6 +471,15 @@ public class UserRestrictionsUtils { } /** + * @return {@code true} only if the restriction is allowed for financed devices and can be set + * by a device owner. Otherwise, {@code false} would be returned. + */ + public static boolean canFinancedDeviceOwnerChange(String restriction) { + return FINANCED_DEVICE_OWNER_RESTRICTIONS.contains(restriction) + && canDeviceOwnerChange(restriction); + } + + /** * Whether given user restriction should be enforced globally. */ public static boolean isGlobal(@UserManagerInternal.OwnerType int restrictionOwnerType, diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index 7e59bd669824..f2b1a7119b84 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java @@ -92,7 +92,7 @@ public class AndroidPackageUtils { AndroidPackageUtils.getAllCodePaths(pkg), pkg.getSdkLibName(), pkg.getSdkLibVersionMajor(), - SharedLibraryInfo.TYPE_SDK, + SharedLibraryInfo.TYPE_SDK_PACKAGE, new VersionedPackage(pkg.getManifestPackageName(), pkg.getLongVersionCode()), null, null, false /* isNative */); 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 317730a9f606..79c5ea2efefe 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -17,6 +17,7 @@ package com.android.server.pm.permission; import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; +import static android.Manifest.permission.POST_NOTIFICATIONS; import static android.Manifest.permission.RECORD_AUDIO; import static android.Manifest.permission.UPDATE_APP_OPS_STATS; import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; @@ -608,6 +609,21 @@ public class PermissionManagerService extends IPermissionManager.Stub { } @Override + public int checkPostNotificationsPermissionGrantedOrLegacyAccess(int uid) { + int granted = PermissionManagerService.this.checkUidPermission(uid, + POST_NOTIFICATIONS); + AndroidPackage pkg = mPackageManagerInt.getPackage(uid); + if (granted != PermissionManager.PERMISSION_GRANTED) { + int flags = PermissionManagerService.this.getPermissionFlags(pkg.getPackageName(), + POST_NOTIFICATIONS, UserHandle.getUserId(uid)); + if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { + return PermissionManager.PERMISSION_GRANTED; + } + } + return granted; + } + + @Override public void startShellPermissionIdentityDelegation(int uid, @NonNull String packageName, @Nullable List<String> permissionNames) { Objects.requireNonNull(packageName, "packageName"); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index d2c4ec4cc5a5..812d7a04dc13 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -63,6 +63,17 @@ public interface PermissionManagerServiceInternal extends PermissionManagerInter int checkUidPermission(int uid, @NonNull String permissionName); /** + * Check whether a particular UID has been granted the POST_NOTIFICATIONS permission, or if + * access should be granted based on legacy access (currently symbolized by the REVIEW_REQUIRED + * permission flag + * + * @param uid the UID + * @return {@code PERMISSION_GRANTED} if the permission is granted, or legacy access is granted, + * {@code PERMISSION_DENIED} otherwise + */ + int checkPostNotificationsPermissionGrantedOrLegacyAccess(int uid); + + /** * Adds a listener for runtime permission state (permissions or flags) changes. * * @param listener The listener. diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 73ec2cd66ac1..77d63100032c 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -33,6 +33,7 @@ import android.metrics.LogMaker; import android.net.Uri; import android.os.BatteryStats; import android.os.Handler; +import android.os.IWakeLockCallback; import android.os.Looper; import android.os.Message; import android.os.PowerManager; @@ -215,14 +216,15 @@ public class Notifier { * Called when a wake lock is acquired. */ public void onWakeLockAcquired(int flags, String tag, String packageName, - int ownerUid, int ownerPid, WorkSource workSource, String historyTag) { + int ownerUid, int ownerPid, WorkSource workSource, String historyTag, + IWakeLockCallback callback) { if (DEBUG) { Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag + "\", packageName=" + packageName + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + ", workSource=" + workSource); } - + notifyWakeLockListener(callback, true); final int monitorType = getBatteryStatsWakeLockMonitorType(flags); if (monitorType >= 0) { try { @@ -300,8 +302,9 @@ public class Notifier { */ public void onWakeLockChanging(int flags, String tag, String packageName, int ownerUid, int ownerPid, WorkSource workSource, String historyTag, - int newFlags, String newTag, String newPackageName, int newOwnerUid, - int newOwnerPid, WorkSource newWorkSource, String newHistoryTag) { + IWakeLockCallback callback, int newFlags, String newTag, String newPackageName, + int newOwnerUid, int newOwnerPid, WorkSource newWorkSource, String newHistoryTag, + IWakeLockCallback newCallback) { final int monitorType = getBatteryStatsWakeLockMonitorType(flags); final int newMonitorType = getBatteryStatsWakeLockMonitorType(newFlags); @@ -323,10 +326,16 @@ public class Notifier { } catch (RemoteException ex) { // Ignore } + } else if (!PowerManagerService.isSameCallback(callback, newCallback)) { + onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag, + null /* Do not notify the old callback */); + onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid, + newWorkSource, newHistoryTag, newCallback /* notify the new callback */); } else { - onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag); + onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag, + callback); onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid, - newWorkSource, newHistoryTag); + newWorkSource, newHistoryTag, newCallback); } } @@ -334,14 +343,15 @@ public class Notifier { * Called when a wake lock is released. */ public void onWakeLockReleased(int flags, String tag, String packageName, - int ownerUid, int ownerPid, WorkSource workSource, String historyTag) { + int ownerUid, int ownerPid, WorkSource workSource, String historyTag, + IWakeLockCallback callback) { if (DEBUG) { Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag + "\", packageName=" + packageName + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + ", workSource=" + workSource); } - + notifyWakeLockListener(callback, false); final int monitorType = getBatteryStatsWakeLockMonitorType(flags); if (monitorType >= 0) { try { @@ -859,6 +869,18 @@ public class Notifier { return enabled && dndOff; } + private void notifyWakeLockListener(IWakeLockCallback callback, boolean isEnabled) { + if (callback != null) { + mHandler.post(() -> { + try { + callback.onStateChanged(isEnabled); + } catch (RemoteException e) { + throw new IllegalArgumentException("Wakelock.mCallback is already dead.", e); + } + }); + } + } + private final class NotifierHandler extends Handler { public NotifierHandler(Looper looper) { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index efcfbdd16e4e..38570727742d 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -66,6 +66,7 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; import android.os.IPowerManager; +import android.os.IWakeLockCallback; import android.os.Looper; import android.os.Message; import android.os.ParcelDuration; @@ -1436,7 +1437,8 @@ public final class PowerManagerService extends SystemService } private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag, - String packageName, WorkSource ws, String historyTag, int uid, int pid) { + String packageName, WorkSource ws, String historyTag, int uid, int pid, + @Nullable IWakeLockCallback callback) { synchronized (mLock) { if (displayId != Display.INVALID_DISPLAY) { final DisplayInfo displayInfo = @@ -1460,11 +1462,12 @@ public final class PowerManagerService extends SystemService boolean notifyAcquire; if (index >= 0) { wakeLock = mWakeLocks.get(index); - if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) { + if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid, callback)) { // Update existing wake lock. This shouldn't happen but is harmless. notifyWakeLockChangingLocked(wakeLock, flags, tag, packageName, - uid, pid, ws, historyTag); - wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid); + uid, pid, ws, historyTag, callback); + wakeLock.updateProperties(flags, tag, packageName, ws, historyTag, uid, pid, + callback); } notifyAcquire = false; } else { @@ -1476,12 +1479,7 @@ public final class PowerManagerService extends SystemService } state.mNumWakeLocks++; wakeLock = new WakeLock(lock, displayId, flags, tag, packageName, ws, historyTag, - uid, pid, state); - try { - lock.linkToDeath(wakeLock, 0); - } catch (RemoteException ex) { - throw new IllegalArgumentException("Wake lock is already dead."); - } + uid, pid, state, callback); mWakeLocks.add(wakeLock); setWakeLockDisabledStateLocked(wakeLock); notifyAcquire = true; @@ -1576,11 +1574,8 @@ public final class PowerManagerService extends SystemService mRequestWaitForNegativeProximity = true; } - try { - wakeLock.mLock.unlinkToDeath(wakeLock, 0); - } catch (NoSuchElementException e) { - Slog.wtf(TAG, "Failed to unlink wakelock", e); - } + wakeLock.unlinkToDeath(); + wakeLock.setDisabled(true); removeWakeLockLocked(wakeLock, index); } } @@ -1650,13 +1645,41 @@ public final class PowerManagerService extends SystemService if (!wakeLock.hasSameWorkSource(ws)) { notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid, - ws, historyTag); + ws, historyTag, null); wakeLock.mHistoryTag = historyTag; wakeLock.updateWorkSource(ws); } } } + private void updateWakeLockCallbackInternal(IBinder lock, IWakeLockCallback callback, + int callingUid) { + synchronized (mLock) { + int index = findWakeLockIndexLocked(lock); + if (index < 0) { + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakeLockCallbackInternal: lock=" + Objects.hashCode(lock) + + " [not found]"); + } + throw new IllegalArgumentException("Wake lock not active: " + lock + + " from uid " + callingUid); + } + + WakeLock wakeLock = mWakeLocks.get(index); + if (DEBUG_SPEW) { + Slog.d(TAG, "updateWakeLockCallbackInternal: lock=" + Objects.hashCode(lock) + + " [" + wakeLock.mTag + "]"); + } + + if (!isSameCallback(callback, wakeLock.mCallback)) { + notifyWakeLockChangingLocked(wakeLock, wakeLock.mFlags, wakeLock.mTag, + wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid, + wakeLock.mWorkSource, wakeLock.mHistoryTag, callback); + wakeLock.mCallback = callback; + } + } + } + @GuardedBy("mLock") private int findWakeLockIndexLocked(IBinder lock) { final int count = mWakeLocks.size(); @@ -1684,7 +1707,7 @@ public final class PowerManagerService extends SystemService wakeLock.mNotifiedAcquired = true; mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource, - wakeLock.mHistoryTag); + wakeLock.mHistoryTag, wakeLock.mCallback); restartNofifyLongTimerLocked(wakeLock); } } @@ -1726,11 +1749,13 @@ public final class PowerManagerService extends SystemService @GuardedBy("mLock") private void notifyWakeLockChangingLocked(WakeLock wakeLock, int flags, String tag, - String packageName, int uid, int pid, WorkSource ws, String historyTag) { + String packageName, int uid, int pid, WorkSource ws, String historyTag, + IWakeLockCallback callback) { if (mSystemReady && wakeLock.mNotifiedAcquired) { mNotifier.onWakeLockChanging(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource, - wakeLock.mHistoryTag, flags, tag, packageName, uid, pid, ws, historyTag); + wakeLock.mHistoryTag, wakeLock.mCallback, flags, tag, packageName, uid, pid, ws, + historyTag, callback); notifyWakeLockLongFinishedLocked(wakeLock); // Changing the wake lock will count as releasing the old wake lock(s) and // acquiring the new ones... we do this because otherwise once a wakelock @@ -1747,7 +1772,7 @@ public final class PowerManagerService extends SystemService wakeLock.mAcquireTime = 0; mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid, - wakeLock.mWorkSource, wakeLock.mHistoryTag); + wakeLock.mWorkSource, wakeLock.mHistoryTag, wakeLock.mCallback); notifyWakeLockLongFinishedLocked(wakeLock); } } @@ -4045,10 +4070,7 @@ public final class PowerManagerService extends SystemService } } } - if (wakeLock.mDisabled != disabled) { - wakeLock.mDisabled = disabled; - return true; - } + return wakeLock.setDisabled(disabled); } return false; } @@ -5041,10 +5063,11 @@ public final class PowerManagerService extends SystemService public boolean mNotifiedAcquired; public boolean mNotifiedLong; public boolean mDisabled; + public IWakeLockCallback mCallback; public WakeLock(IBinder lock, int displayId, int flags, String tag, String packageName, WorkSource workSource, String historyTag, int ownerUid, int ownerPid, - UidState uidState) { + UidState uidState, @Nullable IWakeLockCallback callback) { mLock = lock; mDisplayId = displayId; mFlags = flags; @@ -5055,15 +5078,43 @@ public final class PowerManagerService extends SystemService mOwnerUid = ownerUid; mOwnerPid = ownerPid; mUidState = uidState; + mCallback = callback; + linkToDeath(); } @Override public void binderDied() { + unlinkToDeath(); PowerManagerService.this.handleWakeLockDeath(this); } + private void linkToDeath() { + try { + mLock.linkToDeath(this, 0); + } catch (RemoteException e) { + throw new IllegalArgumentException("Wakelock.mLock is already dead."); + } + } + + @GuardedBy("mLock") + void unlinkToDeath() { + try { + mLock.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Slog.wtf(TAG, "Failed to unlink Wakelock.mLock", e); + } + } + + public boolean setDisabled(boolean disabled) { + if (mDisabled != disabled) { + mDisabled = disabled; + return true; + } else { + return false; + } + } public boolean hasSameProperties(int flags, String tag, WorkSource workSource, - int ownerUid, int ownerPid) { + int ownerUid, int ownerPid, IWakeLockCallback callback) { return mFlags == flags && mTag.equals(tag) && hasSameWorkSource(workSource) @@ -5072,7 +5123,8 @@ public final class PowerManagerService extends SystemService } public void updateProperties(int flags, String tag, String packageName, - WorkSource workSource, String historyTag, int ownerUid, int ownerPid) { + WorkSource workSource, String historyTag, int ownerUid, int ownerPid, + IWakeLockCallback callback) { if (!mPackageName.equals(packageName)) { throw new IllegalStateException("Existing wake lock package name changed: " + mPackageName + " to " + packageName); @@ -5089,6 +5141,7 @@ public final class PowerManagerService extends SystemService mTag = tag; updateWorkSource(workSource); mHistoryTag = historyTag; + mCallback = callback; } public boolean hasSameWorkSource(WorkSource workSource) { @@ -5307,11 +5360,12 @@ public final class PowerManagerService extends SystemService @Override // Binder call public void acquireWakeLockWithUid(IBinder lock, int flags, String tag, - String packageName, int uid, int displayId) { + String packageName, int uid, int displayId, IWakeLockCallback callback) { if (uid < 0) { uid = Binder.getCallingUid(); } - acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null, displayId); + acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid), null, + displayId, callback); } @Override // Binder call @@ -5346,7 +5400,8 @@ public final class PowerManagerService extends SystemService @Override // Binder call public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, - WorkSource ws, String historyTag, int displayId) { + WorkSource ws, String historyTag, int displayId, + @Nullable IWakeLockCallback callback) { if (lock == null) { throw new IllegalArgumentException("lock must not be null"); } @@ -5386,7 +5441,7 @@ public final class PowerManagerService extends SystemService final long ident = Binder.clearCallingIdentity(); try { acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag, - uid, pid); + uid, pid, callback); } finally { Binder.restoreCallingIdentity(ident); } @@ -5395,7 +5450,8 @@ public final class PowerManagerService extends SystemService @Override // Binder call public void acquireWakeLockAsync(IBinder lock, int flags, String tag, String packageName, WorkSource ws, String historyTag) { - acquireWakeLock(lock, flags, tag, packageName, ws, historyTag, Display.INVALID_DISPLAY); + acquireWakeLock(lock, flags, tag, packageName, ws, historyTag, Display.INVALID_DISPLAY, + null); } @Override // Binder call @@ -5463,6 +5519,23 @@ public final class PowerManagerService extends SystemService } @Override // Binder call + public void updateWakeLockCallback(IBinder lock, IWakeLockCallback callback) { + if (lock == null) { + throw new IllegalArgumentException("lock must not be null"); + } + + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); + + final int callingUid = Binder.getCallingUid(); + final long ident = Binder.clearCallingIdentity(); + try { + updateWakeLockCallbackInternal(lock, callback, callingUid); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call public boolean isWakeLockLevelSupported(int level) { final long ident = Binder.clearCallingIdentity(); try { @@ -6510,4 +6583,15 @@ public final class PowerManagerService extends SystemService } }; + static boolean isSameCallback(IWakeLockCallback callback1, + IWakeLockCallback callback2) { + if (callback1 == callback2) { + return true; + } + if (callback1 != null && callback2 != null + && callback1.asBinder() == callback2.asBinder()) { + return true; + } + return false; + } } diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java new file mode 100644 index 000000000000..cc275466a5ff --- /dev/null +++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.resources; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.IResourcesManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteCallback; +import android.os.RemoteException; + +import com.android.server.SystemService; +import com.android.server.am.ActivityManagerService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * A service for managing information about ResourcesManagers + */ +public class ResourcesManagerService extends SystemService { + private ActivityManagerService mActivityManagerService; + + /** + * Initializes the system service. + * <p> + * Subclasses must define a single argument constructor that accepts the context + * and passes it to super. + * </p> + * + * @param context The system server context. + */ + public ResourcesManagerService(@NonNull Context context) { + super(context); + publishBinderService(Context.RESOURCES_SERVICE, mService); + } + + @Override + public void onStart() { + // Intentionally left empty. + } + + private final IBinder mService = new IResourcesManager.Stub() { + @Override + public boolean dumpResources(String process, ParcelFileDescriptor fd, + RemoteCallback callback) throws RemoteException { + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) { + callback.sendResult(null); + throw new SecurityException("dump should only be called by shell"); + } + return mActivityManagerService.dumpResources(process, fd, callback); + } + + @Override + protected void dump(@NonNull FileDescriptor fd, + @NonNull PrintWriter pw, @Nullable String[] args) { + try { + mActivityManagerService.dumpAllResources(ParcelFileDescriptor.dup(fd), pw); + } catch (Exception e) { + pw.println("Exception while trying to dump all resources: " + e.getMessage()); + e.printStackTrace(pw); + } + } + + @Override + public int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, + @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + return (new ResourcesManagerShellCommand(this)).exec( + this, + in.getFileDescriptor(), + out.getFileDescriptor(), + err.getFileDescriptor(), + args); + } + }; + + public void setActivityManagerService( + ActivityManagerService activityManagerService) { + mActivityManagerService = activityManagerService; + } +} diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java new file mode 100644 index 000000000000..7d8336a0d3e9 --- /dev/null +++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.resources; + +import android.content.res.IResourcesManager; +import android.os.ConditionVariable; +import android.os.ParcelFileDescriptor; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.ShellCommand; +import android.util.Slog; + +import java.io.IOException; +import java.io.PrintWriter; + +/** + * Shell command handler for resources related commands + */ +public class ResourcesManagerShellCommand extends ShellCommand { + private static final String TAG = "ResourcesManagerShellCommand"; + + private final IResourcesManager mInterface; + + public ResourcesManagerShellCommand(IResourcesManager anInterface) { + mInterface = anInterface; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter err = getErrPrintWriter(); + try { + switch (cmd) { + case "dump": + return dumpResources(); + default: + return handleDefaultCommands(cmd); + } + } catch (IllegalArgumentException e) { + err.println("Error: " + e.getMessage()); + } catch (RemoteException e) { + err.println("Remote exception: " + e); + } + return -1; + } + + private int dumpResources() throws RemoteException { + String processId = getNextArgRequired(); + try { + ConditionVariable lock = new ConditionVariable(); + RemoteCallback + finishCallback = new RemoteCallback(result -> lock.open(), null); + + if (!mInterface.dumpResources(processId, + ParcelFileDescriptor.dup(getOutFileDescriptor()), finishCallback)) { + getErrPrintWriter().println("RESOURCES DUMP FAILED on process " + processId); + return -1; + } + lock.block(5000); + return 0; + } catch (IOException e) { + Slog.e(TAG, "Exception while dumping resources", e); + getErrPrintWriter().println("Exception while dumping resources: " + e.getMessage()); + } + return -1; + } + + @Override + public void onHelp() { + final PrintWriter out = getOutPrintWriter(); + out.println("Resources manager commands:"); + out.println(" help"); + out.println(" Print this help text."); + out.println(" dump <PROCESS>"); + out.println(" Dump the Resources objects in use as well as the history of Resources"); + + } +} diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index e71ff784dc23..8a87c96fcaaa 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -47,6 +47,7 @@ import android.content.pm.ResolveInfo; import android.graphics.drawable.Icon; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; +import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; @@ -157,6 +158,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private final SparseArray<UiState> mDisplayUiState = new SparseArray<>(); @GuardedBy("mLock") private IUdfpsHbmListener mUdfpsHbmListener; + @GuardedBy("mLock") + private IBiometricContextListener mBiometricContextListener; @GuardedBy("mCurrentRequestAddTilePackages") private final ArrayMap<String, Long> mCurrentRequestAddTilePackages = new ArrayMap<>(); @@ -894,6 +897,20 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override + public void setBiometicContextListener(IBiometricContextListener listener) { + enforceStatusBarService(); + synchronized (mLock) { + mBiometricContextListener = listener; + } + if (mBar != null) { + try { + mBar.setBiometicContextListener(listener); + } catch (RemoteException ex) { + } + } + } + + @Override public void setUdfpsHbmListener(IUdfpsHbmListener listener) { enforceStatusBarService(); if (mBar != null) { @@ -1315,6 +1332,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D mHandler.post(() -> { synchronized (mLock) { setUdfpsHbmListener(mUdfpsHbmListener); + setBiometicContextListener(mBiometricContextListener); } }); } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 3908874d854d..5f04b7e2621a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -46,6 +46,9 @@ 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.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION; +import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE; +import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; import static android.content.Intent.CATEGORY_LAUNCHER; @@ -241,6 +244,7 @@ import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; import android.app.WaitResult; import android.app.WindowConfiguration; +import android.app.admin.DevicePolicyManager; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ActivityRelaunchItem; @@ -272,6 +276,7 @@ import android.graphics.Bitmap; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.hardware.HardwareBuffer; import android.net.Uri; import android.os.Binder; @@ -538,6 +543,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Tracking splash screen status from previous activity boolean mSplashScreenStyleEmpty = false; + Drawable mEnterpriseThumbnailDrawable; + + private void updateEnterpriseThumbnailDrawable(Context context) { + DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + mEnterpriseThumbnailDrawable = dpm.getDrawable( + WORK_PROFILE_ICON, OUTLINE, PROFILE_SWITCH_ANIMATION, + () -> context.getDrawable(R.drawable.ic_corp_badge)); + } + static final int LAUNCH_SOURCE_TYPE_SYSTEM = 1; static final int LAUNCH_SOURCE_TYPE_HOME = 2; static final int LAUNCH_SOURCE_TYPE_SYSTEMUI = 3; @@ -716,6 +730,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Nullable private Rect mLetterboxBoundsForFixedOrientationAndAspectRatio; + // Whether the activity is eligible to be letterboxed for fixed orientation with respect to its + // requested orientation, even when it's letterbox for another reason (e.g., size compat mode) + // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false. + private boolean mIsEligibleForFixedOrientationLetterbox; + // State of the Camera app compat control which is used to correct stretched viewfinder // in apps that don't handle all possible configurations and changes between them correctly. @CameraCompatControlState @@ -1925,6 +1944,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName); mActivityRecordInputSink = new ActivityRecordInputSink(this); + + updateEnterpriseThumbnailDrawable(mAtmService.mUiContext); } /** @@ -6929,7 +6950,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override void prepareSurfaces() { - final boolean show = isVisible() || isAnimating(TRANSITION | PARENTS, + final boolean show = isVisible() || isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS); if (mSurfaceControl != null) { @@ -6988,12 +7009,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } final Rect frame = win.getRelativeFrame(); - final int thumbnailDrawableRes = task.mUserId == mWmService.mCurrentUserId - ? R.drawable.ic_account_circle - : R.drawable.ic_corp_badge; - final HardwareBuffer thumbnail = - getDisplayContent().mAppTransition - .createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame); + final Drawable thumbnailDrawable = task.mUserId == mWmService.mCurrentUserId + ? mAtmService.mUiContext.getDrawable(R.drawable.ic_account_circle) + : mEnterpriseThumbnailDrawable; + final HardwareBuffer thumbnail = getDisplayContent().mAppTransition + .createCrossProfileAppsThumbnail(thumbnailDrawable, frame); if (thumbnail == null) { return; } @@ -7627,6 +7647,24 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * Whether this activity is eligible for letterbox eduction. + * + * <p>Conditions that need to be met: + * + * <ul> + * <li>{@link LetterboxConfiguration#getIsEducationEnabled} is true. + * <li>The activity is eligible for fixed orientation letterbox. + * <li>The activity is in fullscreen. + * </ul> + */ + // TODO(b/215316431): Add tests + boolean isEligibleForLetterboxEducation() { + return mWmService.mLetterboxConfiguration.getIsEducationEnabled() + && mIsEligibleForFixedOrientationLetterbox + && getWindowingMode() == WINDOWING_MODE_FULLSCREEN; + } + + /** * In some cases, applying insets to bounds changes the orientation. For example, if a * close-to-square display rotates to portrait to respect a portrait orientation activity, after * insets such as the status and nav bars are applied, the activity may actually have a @@ -7688,6 +7726,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig, int windowingMode) { mLetterboxBoundsForFixedOrientationAndAspectRatio = null; + mIsEligibleForFixedOrientationLetterbox = false; final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); final Rect stableBounds = new Rect(); // If orientation is respected when insets are applied, then stableBounds will be empty. @@ -7727,8 +7766,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // make it fit the available bounds by scaling down its bounds. final int forcedOrientation = getRequestedConfigurationOrientation(); - if (forcedOrientation == ORIENTATION_UNDEFINED - || (forcedOrientation == parentOrientation && orientationRespectedWithInsets)) { + mIsEligibleForFixedOrientationLetterbox = forcedOrientation != ORIENTATION_UNDEFINED + && forcedOrientation != parentOrientation; + + if (!mIsEligibleForFixedOrientationLetterbox && (forcedOrientation == ORIENTATION_UNDEFINED + || orientationRespectedWithInsets)) { return; } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index d5abe4f8ed02..56adcfd76403 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -93,7 +93,6 @@ import static com.android.server.wm.WindowManagerInternal.KeyguardExitAnimationS import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE; -import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -101,6 +100,7 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.hardware.HardwareBuffer; import android.os.Binder; import android.os.Debug; @@ -539,8 +539,8 @@ public class AppTransition implements Dump { * animation. */ HardwareBuffer createCrossProfileAppsThumbnail( - @DrawableRes int thumbnailDrawableRes, Rect frame) { - return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawableRes, frame); + Drawable thumbnailDrawable, Rect frame) { + return mTransitionAnimation.createCrossProfileAppsThumbnail(thumbnailDrawable, frame); } Animation createCrossProfileAppsThumbnailAnimationLocked(Rect appRect) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 4009220d1008..c87027dde3ad 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5486,26 +5486,46 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void updateKeepClearAreas() { + final List<Rect> restrictedKeepClearAreas = new ArrayList(); + final List<Rect> unrestrictedKeepClearAreas = new ArrayList(); + getKeepClearAreas(restrictedKeepClearAreas, unrestrictedKeepClearAreas); mWmService.mDisplayNotificationController.dispatchKeepClearAreasChanged( - this, getKeepClearAreas()); + this, restrictedKeepClearAreas, unrestrictedKeepClearAreas); } /** - * Returns all keep-clear areas from visible windows on this display. + * Fills {@param outRestricted} with all keep-clear areas from visible, relevant windows + * on this display, which set restricted keep-clear areas. + * Fills {@param outUnrestricted} with keep-clear areas from visible, relevant windows on this + * display, which set unrestricted keep-clear areas. + * + * For context on restricted vs unrestricted keep-clear areas, see + * {@link android.Manifest.permission.USE_UNRESTRICTED_KEEP_CLEAR_AREAS}. */ - ArrayList<Rect> getKeepClearAreas() { - final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>(); + void getKeepClearAreas(List<Rect> outRestricted, List<Rect> outUnrestricted) { final Matrix tmpMatrix = new Matrix(); final float[] tmpFloat9 = new float[9]; forAllWindows(w -> { if (w.isVisible() && !w.inPinnedWindowingMode()) { - keepClearAreas.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9)); + if (w.mSession.mSetsUnrestrictedKeepClearAreas) { + outUnrestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9)); + } else { + outRestricted.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9)); + } } // We stop traversing when we reach the base of a fullscreen app. return w.getWindowType() == TYPE_BASE_APPLICATION && w.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; }, true); + } + + /** + * Returns all keep-clear areas from visible, relevant windows on this display. + */ + ArrayList<Rect> getKeepClearAreas() { + final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>(); + getKeepClearAreas(keepClearAreas, keepClearAreas); return keepClearAreas; } diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java index 276dbe9c844a..e18d5396831a 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java +++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java @@ -120,12 +120,13 @@ class DisplayWindowListenerController { mDisplayListeners.finishBroadcast(); } - void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> keepClearAreas) { + void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> restricted, + List<Rect> unrestricted) { int count = mDisplayListeners.beginBroadcast(); for (int i = 0; i < count; ++i) { try { mDisplayListeners.getBroadcastItem(i).onKeepClearAreasChanged( - display.mDisplayId, keepClearAreas); + display.mDisplayId, restricted, unrestricted); } catch (RemoteException e) { } } diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 10776abee51e..1e121737e2ac 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -32,6 +32,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_ import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityTaskManager; import android.app.StatusBarManager; import android.graphics.Insets; import android.graphics.Rect; @@ -134,7 +135,7 @@ class InsetsPolicy { /** Updates the target which can control system bars. */ void updateBarControlTarget(@Nullable WindowState focusedWin) { - if (mFocusedWin != focusedWin){ + if (mFocusedWin != focusedWin) { abortTransient(); } mFocusedWin = focusedWin; @@ -156,7 +157,7 @@ class InsetsPolicy { } boolean isHidden(@InternalInsetsType int type) { - final InsetsSourceProvider provider = mStateController.peekSourceProvider(type); + final InsetsSourceProvider provider = mStateController.peekSourceProvider(type); return provider != null && provider.hasWindow() && !provider.getSource().isVisible(); } @@ -181,6 +182,10 @@ class InsetsPolicy { mShowingTransientTypes.toArray(), isGestureOnSystemBar); } updateBarControlTarget(mFocusedWin); + dispatchTransientSystemBarsVisibilityChanged( + mFocusedWin, + isTransient(ITYPE_STATUS_BAR) || isTransient(ITYPE_NAVIGATION_BAR), + isGestureOnSystemBar); // The leashes can be created while updating bar control target. The surface transaction // of the new leashes might not be applied yet. The callback posted here ensures we can @@ -198,6 +203,12 @@ class InsetsPolicy { if (mShowingTransientTypes.size() == 0) { return; } + + dispatchTransientSystemBarsVisibilityChanged( + mFocusedWin, + /* areVisible= */ false, + /* wereRevealedFromSwipeOnSystemBar= */ false); + startAnimation(false /* show */, () -> { synchronized (mDisplayContent.mWmService.mGlobalLock) { for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) { @@ -373,6 +384,11 @@ class InsetsPolicy { mDisplayContent.getDisplayId(), mShowingTransientTypes.toArray()); } mShowingTransientTypes.clear(); + + dispatchTransientSystemBarsVisibilityChanged( + mFocusedWin, + /* areVisible= */ false, + /* wereRevealedFromSwipeOnSystemBar= */ false); } private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin, @@ -521,6 +537,32 @@ class InsetsPolicy { listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show); } + private void dispatchTransientSystemBarsVisibilityChanged( + @Nullable WindowState focusedWindow, + boolean areVisible, + boolean wereRevealedFromSwipeOnSystemBar) { + if (focusedWindow == null) { + return; + } + + Task task = focusedWindow.getTask(); + if (task == null) { + return; + } + + int taskId = task.mTaskId; + boolean isValidTaskId = taskId != ActivityTaskManager.INVALID_TASK_ID; + if (!isValidTaskId) { + return; + } + + mDisplayContent.mWmService.mTaskSystemBarsListenerController + .dispatchTransientSystemBarVisibilityChanged( + taskId, + areVisible, + wereRevealedFromSwipeOnSystemBar); + } + private class BarWindow { private final int mId; diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 1955e30aab30..ad2767c41e82 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -126,6 +126,9 @@ final class LetterboxConfiguration { @LetterboxReachabilityPosition private volatile int mLetterboxPositionForReachability; + // Whether education is allowed for letterboxed fullscreen apps. + private boolean mIsEducationEnabled; + LetterboxConfiguration(Context systemUiContext) { mContext = systemUiContext; mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat( @@ -143,6 +146,8 @@ final class LetterboxConfiguration { R.bool.config_letterboxIsReachabilityEnabled); mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext); mLetterboxPositionForReachability = mDefaultPositionForReachability; + mIsEducationEnabled = mContext.getResources().getBoolean( + R.bool.config_letterboxIsEducationEnabled); } /** @@ -501,4 +506,27 @@ final class LetterboxConfiguration { mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0); } + /** + * Whether education is allowed for letterboxed fullscreen apps. + */ + boolean getIsEducationEnabled() { + return mIsEducationEnabled; + } + + /** + * Overrides whether education is allowed for letterboxed fullscreen apps. + */ + void setIsEducationEnabled(boolean enabled) { + mIsEducationEnabled = enabled; + } + + /** + * Resets whether education is allowed for letterboxed fullscreen apps to + * {@link R.bool.config_letterboxIsEducationEnabled}. + */ + void resetIsEducationEnabled() { + mIsEducationEnabled = mContext.getResources().getBoolean( + R.bool.config_letterboxIsEducationEnabled); + } + } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 98acc4607d18..2ae2b4370522 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS; import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; +import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY; import static android.app.ActivityTaskManager.INVALID_TASK_ID; @@ -114,6 +115,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { private String mRelayoutTag; private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities(); private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0]; + final boolean mSetsUnrestrictedKeepClearAreas; public Session(WindowManagerService service, IWindowSessionCallback callback) { mService = service; @@ -132,6 +134,9 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { == PERMISSION_GRANTED; mCanStartTasksFromRecents = service.mContext.checkCallingOrSelfPermission( START_TASKS_FROM_RECENTS) == PERMISSION_GRANTED; + mSetsUnrestrictedKeepClearAreas = + service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_KEEP_CLEAR_AREAS) + == PERMISSION_GRANTED; mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications; mDragDropController = mService.mDragDropController; StringBuilder sb = new StringBuilder(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 7d06526e18b5..97735a29b027 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3434,6 +3434,9 @@ class Task extends TaskFragment { // Whether the direct top activity is in size compat mode on foreground. info.topActivityInSizeCompat = isTopActivityResumed && mReuseActivitiesReport.top.inSizeCompatMode(); + // Whether the direct top activity is eligible for letterbox education. + info.topActivityEligibleForLetterboxEducation = isTopActivityResumed + && mReuseActivitiesReport.top.isEligibleForLetterboxEducation(); // Whether the direct top activity requested showing camera compat control. info.cameraCompatControlState = isTopActivityResumed ? mReuseActivitiesReport.top.getCameraCompatControlState() diff --git a/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java b/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java new file mode 100644 index 000000000000..acb6061de93f --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskSystemBarsListenerController.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import com.android.internal.os.BackgroundThread; +import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener; + +import java.util.HashSet; +import java.util.concurrent.Executor; + +/** + * Manages dispatch of task system bar changes to interested listeners. All invocations must be + * performed while the {@link WindowManagerService#getWindowManagerLock() Window Manager Lock} is + * held. + */ +final class TaskSystemBarsListenerController { + + private final HashSet<TaskSystemBarsListener> mListeners = new HashSet<>(); + private final Executor mBackgroundExecutor; + + TaskSystemBarsListenerController() { + this.mBackgroundExecutor = BackgroundThread.getExecutor(); + } + + void registerListener(TaskSystemBarsListener listener) { + mListeners.add(listener); + } + + void unregisterListener(TaskSystemBarsListener listener) { + mListeners.remove(listener); + } + + void dispatchTransientSystemBarVisibilityChanged( + int taskId, + boolean visible, + boolean wereRevealedFromSwipeOnSystemBar) { + HashSet<TaskSystemBarsListener> localListeners; + localListeners = new HashSet<>(mListeners); + + mBackgroundExecutor.execute(() -> { + for (TaskSystemBarsListener listener : localListeners) { + listener.onTransientSystemBarsVisibilityChanged( + taskId, + visible, + wereRevealedFromSwipeOnSystemBar); + } + }); + } +} diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index ded58f48b207..ad4594873cf0 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1039,6 +1039,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe " Rejecting as detached: %s", wc); continue; } + // The level of transition target should be at least window token. + if (wc.asWindowState() != null) continue; final ChangeInfo changeInfo = changes.get(wc); diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 20fa7a9da256..4900f9292f2a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -256,6 +256,25 @@ public abstract class WindowManagerInternal { } /** + * An interface to be notified when the system bars for a task change. + */ + public interface TaskSystemBarsListener { + + /** + * Called when the visibility of the system bars of a task change. + * + * @param taskId the identifier of the task. + * @param visible if the transient system bars are visible. + * @param wereRevealedFromSwipeOnSystemBar if the transient bars were revealed due to a + * swipe gesture on a system bar. + */ + void onTransientSystemBarsVisibilityChanged( + int taskId, + boolean visible, + boolean wereRevealedFromSwipeOnSystemBar); + } + + /** * An interface to be notified when keyguard exit animation should start. */ public interface KeyguardExitAnimationStartListener { @@ -519,6 +538,20 @@ public abstract class WindowManagerInternal { public abstract void registerAppTransitionListener(AppTransitionListener listener); /** + * Registers a listener to be notified to when the system bars of a task changes. + * + * @param listener The listener to register. + */ + public abstract void registerTaskSystemBarsListener(TaskSystemBarsListener listener); + + /** + * Registers a listener to be notified to when the system bars of a task changes. + * + * @param listener The listener to unregister. + */ + public abstract void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener); + + /** * Registers a listener to be notified to start the keyguard exit animation. * * @param listener The listener to register. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b37cb4f286f9..50467a98f5ad 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -683,6 +683,7 @@ public class WindowManagerService extends IWindowManager.Stub () -> mDisplayRotationController = null; final DisplayWindowListenerController mDisplayNotificationController; + final TaskSystemBarsListenerController mTaskSystemBarsListenerController; boolean mDisplayFrozen = false; long mDisplayFreezeTime = 0; @@ -1265,6 +1266,7 @@ public class WindowManagerService extends IWindowManager.Stub mScreenFrozenLock.setReferenceCounted(false); mDisplayNotificationController = new DisplayWindowListenerController(this); + mTaskSystemBarsListenerController = new TaskSystemBarsListenerController(); mActivityManager = ActivityManager.getService(); mActivityTaskManager = ActivityTaskManager.getService(); @@ -7591,6 +7593,20 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void registerTaskSystemBarsListener(TaskSystemBarsListener listener) { + synchronized (mGlobalLock) { + mTaskSystemBarsListenerController.registerListener(listener); + } + } + + @Override + public void unregisterTaskSystemBarsListener(TaskSystemBarsListener listener) { + synchronized (mGlobalLock) { + mTaskSystemBarsListenerController.unregisterListener(listener); + } + } + + @Override public void registerKeyguardExitAnimationStartListener( KeyguardExitAnimationStartListener listener) { synchronized (mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 0f8587c99958..1cf4c1b0fbb2 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -822,6 +822,29 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } + private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException { + String arg = getNextArg(); + final boolean enabled; + switch (arg) { + case "true": + case "1": + enabled = true; + break; + case "false": + case "0": + enabled = false; + break; + default: + getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg); + return -1; + } + + synchronized (mInternal.mGlobalLock) { + mLetterboxConfiguration.setIsEducationEnabled(enabled); + } + return 0; + } + private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException { if (peekNextArg() == null) { getErrPrintWriter().println("Error: No arguments provided."); @@ -859,6 +882,9 @@ public class WindowManagerShellCommand extends ShellCommand { case "--defaultPositionForReachability": runSetLetterboxDefaultPositionForReachability(pw); break; + case "--isEducationEnabled": + runSetLetterboxIsEducationEnabled(pw); + break; default: getErrPrintWriter().println( "Error: Unrecognized letterbox style option: " + arg); @@ -903,6 +929,9 @@ public class WindowManagerShellCommand extends ShellCommand { case "defaultPositionForReachability": mLetterboxConfiguration.getDefaultPositionForReachability(); break; + case "isEducationEnabled": + mLetterboxConfiguration.getIsEducationEnabled(); + break; default: getErrPrintWriter().println( "Error: Unrecognized letterbox style option: " + arg); @@ -998,6 +1027,7 @@ public class WindowManagerShellCommand extends ShellCommand { mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier(); mLetterboxConfiguration.resetIsReachabilityEnabled(); mLetterboxConfiguration.resetDefaultPositionForReachability(); + mLetterboxConfiguration.resetIsEducationEnabled(); } } @@ -1014,6 +1044,8 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println("Default position for reachability: " + LetterboxConfiguration.letterboxReachabilityPositionToString( mLetterboxConfiguration.getDefaultPositionForReachability())); + pw.println("Is education enabled: " + + mLetterboxConfiguration.getIsEducationEnabled()); pw.println("Background type: " + LetterboxConfiguration.letterboxBackgroundTypeToString( @@ -1154,10 +1186,12 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" --defaultPositionForReachability [left|center|right]"); pw.println(" Default horizontal position of app window when reachability is."); pw.println(" enabled."); + pw.println(" --isEducationEnabled [true|1|false|0]"); + pw.println(" Whether education is allowed for letterboxed fullscreen apps."); pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType"); pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha"); pw.println(" |horizontalPositionMultiplier|isReachabilityEnabled"); - pw.println(" |defaultPositionMultiplierForReachability]"); + pw.println(" isEducationEnabled||defaultPositionMultiplierForReachability]"); pw.println(" Resets overrides to default values for specified properties separated"); pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'."); pw.println(" If no arguments provided, all values will be reset."); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 95ef5f76b765..99abf440910e 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -103,6 +103,7 @@ cc_defaults { "libappfuse", "libbinder_ndk", "libbinder", + "libchrome", "libcutils", "libcrypto", "liblog", diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp index 43018a900f4c..adc91fc3f2e8 100644 --- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp +++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp @@ -186,7 +186,7 @@ static std::map<int, int> KEY_CODE_MAPPING = { }; /** Creates a new uinput device and assigns a file descriptor. */ -static int openUinput(const char* readableName, jint vendorId, jint productId, +static int openUinput(const char* readableName, jint vendorId, jint productId, const char* phys, DeviceType deviceType, jint screenHeight, jint screenWidth) { android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK))); if (fd < 0) { @@ -194,6 +194,8 @@ static int openUinput(const char* readableName, jint vendorId, jint productId, return -errno; } + ioctl(fd, UI_SET_PHYS, phys); + ioctl(fd, UI_SET_EVBIT, EV_KEY); ioctl(fd, UI_SET_EVBIT, EV_SYN); switch (deviceType) { @@ -295,28 +297,30 @@ static int openUinput(const char* readableName, jint vendorId, jint productId, return fd.release(); } -static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, +static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId, jstring phys, DeviceType deviceType, int screenHeight, int screenWidth) { ScopedUtfChars readableName(env, name); - return openUinput(readableName.c_str(), vendorId, productId, deviceType, screenHeight, - screenWidth); + ScopedUtfChars readablePhys(env, phys); + return openUinput(readableName.c_str(), vendorId, productId, readablePhys.c_str(), deviceType, + screenHeight, screenWidth); } static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId, - jint productId) { - return openUinputJni(env, name, vendorId, productId, DeviceType::KEYBOARD, /* screenHeight */ 0, - /* screenWidth */ 0); + jint productId, jstring phys) { + return openUinputJni(env, name, vendorId, productId, phys, DeviceType::KEYBOARD, + /* screenHeight */ 0, /* screenWidth */ 0); } static int nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId, - jint productId) { - return openUinputJni(env, name, vendorId, productId, DeviceType::MOUSE, /* screenHeight */ 0, - /* screenWidth */ 0); + jint productId, jstring phys) { + return openUinputJni(env, name, vendorId, productId, phys, DeviceType::MOUSE, + /* screenHeight */ 0, /* screenWidth */ 0); } static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId, - jint productId, jint height, jint width) { - return openUinputJni(env, name, vendorId, productId, DeviceType::TOUCHSCREEN, height, width); + jint productId, jstring phys, jint height, jint width) { + return openUinputJni(env, name, vendorId, productId, phys, DeviceType::TOUCHSCREEN, height, + width); } static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) { @@ -435,9 +439,11 @@ static bool nativeWriteScrollEvent(JNIEnv* env, jobject thiz, jint fd, jfloat xA } static JNINativeMethod methods[] = { - {"nativeOpenUinputKeyboard", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputKeyboard}, - {"nativeOpenUinputMouse", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputMouse}, - {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IIII)I", + {"nativeOpenUinputKeyboard", "(Ljava/lang/String;IILjava/lang/String;)I", + (void*)nativeOpenUinputKeyboard}, + {"nativeOpenUinputMouse", "(Ljava/lang/String;IILjava/lang/String;)I", + (void*)nativeOpenUinputMouse}, + {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IILjava/lang/String;II)I", (void*)nativeOpenUinputTouchscreen}, {"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput}, {"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent}, diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index df5fb2827a16..8cb27e179c19 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -457,6 +457,9 @@ void NativeInputManager::dump(std::string& dump) { mInputManager->getReader().dump(dump); dump += "\n"; + mInputManager->getUnwantedInteractionBlocker().dump(dump); + dump += "\n"; + mInputManager->getClassifier().dump(dump); dump += "\n"; @@ -704,6 +707,7 @@ void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) { void NativeInputManager::notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) { ATRACE_CALL(); + mInputManager->getUnwantedInteractionBlocker().notifyInputDevicesChanged(inputDevices); JNIEnv* env = jniEnv(); size_t count = inputDevices.size(); diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 0da8f7ef0dea..166a0f5272cf 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -244,6 +244,9 @@ bool hasLatLong(const GnssLocation_V2_0& location) { return hasLatLong(location.v1_0); } +bool isSvStatusRegistered = false; +bool isNmeaRegistered = false; + } // namespace static inline jboolean boolToJbool(bool value) { @@ -505,6 +508,13 @@ uint32_t GnssCallback::getConstellationType( template <class T_list, class T_sv_info> Return<void> GnssCallback::gnssSvStatusCbImpl(const T_list& svStatus) { + // In HIDL or AIDL v1, if no listener is registered, do not report svInfoList to the framework. + if (gnssHalAidl == nullptr || gnssHalAidl->getInterfaceVersion() == 1) { + if (!isSvStatusRegistered) { + return Void(); + } + } + JNIEnv* env = getJniEnv(); uint32_t listSize = getGnssSvInfoListSize(svStatus); @@ -566,8 +576,12 @@ Return<void> GnssCallback::gnssSvStatusCbImpl(const T_list& svStatus) { return Void(); } -Return<void> GnssCallback::gnssNmeaCb( - int64_t timestamp, const ::android::hardware::hidl_string& nmea) { +Return<void> GnssCallback::gnssNmeaCb(int64_t timestamp, + const ::android::hardware::hidl_string& nmea) { + // In HIDL, if no listener is registered, do not report nmea to the framework. + if (!isNmeaRegistered) { + return Void(); + } JNIEnv* env = getJniEnv(); /* * The Java code will call back to read these values. @@ -680,6 +694,12 @@ Status GnssCallbackAidl::gnssLocationCb(const GnssLocationAidl& location) { } Status GnssCallbackAidl::gnssNmeaCb(const int64_t timestamp, const std::string& nmea) { + // In AIDL v1, if no listener is registered, do not report nmea to the framework. + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() == 1) { + if (!isNmeaRegistered) { + return Status::ok(); + } + } JNIEnv* env = getJniEnv(); /* * The Java code will call back to read these values. @@ -1504,11 +1524,14 @@ static jboolean android_location_gnss_hal_GnssNative_set_position_mode( JNIEnv* /* env */, jclass, jint mode, jint recurrence, jint min_interval, jint preferred_accuracy, jint preferred_time, jboolean low_power_mode) { if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { - auto status = gnssHalAidl->setPositionMode(static_cast<IGnssAidl::GnssPositionMode>(mode), - static_cast<IGnssAidl::GnssPositionRecurrence>( - recurrence), - min_interval, preferred_accuracy, preferred_time, - low_power_mode); + IGnssAidl::PositionModeOptions options; + options.mode = static_cast<IGnssAidl::GnssPositionMode>(mode); + options.recurrence = static_cast<IGnssAidl::GnssPositionRecurrence>(recurrence); + options.minIntervalMs = min_interval; + options.preferredAccuracyMeters = preferred_accuracy; + options.preferredTimeMs = preferred_time; + options.lowPowerMode = low_power_mode; + auto status = gnssHalAidl->setPositionMode(options); return checkAidlStatus(status, "IGnssAidl setPositionMode() failed."); } @@ -1559,6 +1582,58 @@ static jboolean android_location_gnss_hal_GnssNative_stop(JNIEnv* /* env */, jcl return checkHidlReturn(result, "IGnss stop() failed."); } +static jboolean android_location_gnss_hal_GnssNative_start_sv_status_collection(JNIEnv* /* env */, + jclass) { + isSvStatusRegistered = true; + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + auto status = gnssHalAidl->startSvStatus(); + return checkAidlStatus(status, "IGnssAidl startSvStatus() failed."); + } + if (gnssHal == nullptr) { + return JNI_FALSE; + } + return JNI_TRUE; +} + +static jboolean android_location_gnss_hal_GnssNative_stop_sv_status_collection(JNIEnv* /* env */, + jclass) { + isSvStatusRegistered = false; + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + auto status = gnssHalAidl->stopSvStatus(); + return checkAidlStatus(status, "IGnssAidl stopSvStatus() failed."); + } + if (gnssHal == nullptr) { + return JNI_FALSE; + } + return JNI_TRUE; +} + +static jboolean android_location_gnss_hal_GnssNative_start_nmea_message_collection( + JNIEnv* /* env */, jclass) { + isNmeaRegistered = true; + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + auto status = gnssHalAidl->startNmea(); + return checkAidlStatus(status, "IGnssAidl startNmea() failed."); + } + if (gnssHal == nullptr) { + return JNI_FALSE; + } + return JNI_TRUE; +} + +static jboolean android_location_gnss_hal_GnssNative_stop_nmea_message_collection(JNIEnv* /* env */, + jclass) { + isNmeaRegistered = false; + if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { + auto status = gnssHalAidl->stopNmea(); + return checkAidlStatus(status, "IGnssAidl stopNmea() failed."); + } + if (gnssHal == nullptr) { + return JNI_FALSE; + } + return JNI_TRUE; +} + static void android_location_gnss_hal_GnssNative_delete_aiding_data(JNIEnv* /* env */, jclass, jint flags) { if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) { @@ -1986,7 +2061,7 @@ static SingleSatCorrection_V1_0 getSingleSatCorrection_1_0_withoutConstellation( jfloat eplMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEpl); jfloat eplUncMeters = env->CallFloatMethod(singleSatCorrectionObj, method_correctionSatEplUnc); uint16_t corrFlags = static_cast<uint16_t>(correctionFlags); - jobject reflectingPlaneObj; + jobject reflectingPlaneObj = nullptr; bool has_ref_plane = (corrFlags & GnssSingleSatCorrectionFlags::HAS_REFLECTING_PLANE) != 0; if (has_ref_plane) { reflectingPlaneObj = @@ -2010,6 +2085,7 @@ static SingleSatCorrection_V1_0 getSingleSatCorrection_1_0_withoutConstellation( .azimuthDegrees = azimuthDegreeRefPlane, }; } + env->DeleteLocalRef(reflectingPlaneObj); SingleSatCorrection_V1_0 singleSatCorrection = { .singleSatCorrectionFlags = corrFlags, @@ -2041,6 +2117,7 @@ static void getSingleSatCorrectionList_1_1(JNIEnv* env, jobject singleSatCorrect }; list[i] = singleSatCorrection_1_1; + env->DeleteLocalRef(singleSatCorrectionObj); } } @@ -2058,6 +2135,7 @@ static void getSingleSatCorrectionList_1_0(JNIEnv* env, jobject singleSatCorrect singleSatCorrection.constellation = static_cast<GnssConstellationType_V1_0>(constType), list[i] = singleSatCorrection; + env->DeleteLocalRef(singleSatCorrectionObj); } } @@ -2128,6 +2206,7 @@ static jboolean android_location_gnss_hal_GnssNative_inject_measurement_correcti hidl_vec<SingleSatCorrection_V1_0> list(len); getSingleSatCorrectionList_1_0(env, singleSatCorrectionList, list); + env->DeleteLocalRef(singleSatCorrectionList); measurementCorrections_1_0.satCorrections = list; auto result = gnssCorrectionsIface_V1_0->setCorrections(measurementCorrections_1_0); @@ -2362,6 +2441,16 @@ static const JNINativeMethod sLocationProviderMethods[] = { {"native_is_gnss_visibility_control_supported", "()Z", reinterpret_cast<void*>( android_location_gnss_hal_GnssNative_is_gnss_visibility_control_supported)}, + {"native_start_sv_status_collection", "()Z", + reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_start_sv_status_collection)}, + {"native_stop_sv_status_collection", "()Z", + reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_stop_sv_status_collection)}, + {"native_start_nmea_message_collection", "()Z", + reinterpret_cast<void*>( + android_location_gnss_hal_GnssNative_start_nmea_message_collection)}, + {"native_stop_nmea_message_collection", "()Z", + reinterpret_cast<void*>( + android_location_gnss_hal_GnssNative_stop_nmea_message_collection)}, }; static const JNINativeMethod sBatchingMethods[] = { diff --git a/services/core/jni/gnss/AGnssRil.cpp b/services/core/jni/gnss/AGnssRil.cpp index d760b4d2195e..424ffd463713 100644 --- a/services/core/jni/gnss/AGnssRil.cpp +++ b/services/core/jni/gnss/AGnssRil.cpp @@ -41,7 +41,7 @@ jboolean AGnssRil::setCallback(const std::unique_ptr<AGnssRilCallback>& callback jboolean AGnssRil::setSetId(jint type, const jstring& setid_string) { JNIEnv* env = getJniEnv(); ScopedJniString jniSetId{env, setid_string}; - auto status = mIAGnssRil->setSetId((IAGnssRil::SetIDType)type, jniSetId.c_str()); + auto status = mIAGnssRil->setSetId((IAGnssRil::SetIdType)type, jniSetId.c_str()); return checkAidlStatus(status, "IAGnssRilAidl setSetId() failed."); } diff --git a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp index fbc000b25d26..99d06eb062da 100644 --- a/services/core/jni/gnss/GnssAntennaInfoCallback.cpp +++ b/services/core/jni/gnss/GnssAntennaInfoCallback.cpp @@ -226,17 +226,18 @@ jobject GnssAntennaInfoCallback::translateSingleGnssAntennaInfo( env->NewObject(class_gnssAntennaInfoBuilder, method_gnssAntennaInfoBuilderCtor); // Set fields - env->CallObjectMethod(gnssAntennaInfoBuilderObject, - method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz, - gnssAntennaInfo.carrierFrequencyMHz); - env->CallObjectMethod(gnssAntennaInfoBuilderObject, - method_gnssAntennaInfoBuilderSetPhaseCenterOffset, phaseCenterOffset); - env->CallObjectMethod(gnssAntennaInfoBuilderObject, - method_gnssAntennaInfoBuilderSetPhaseCenterVariationCorrections, - phaseCenterVariationCorrections); - env->CallObjectMethod(gnssAntennaInfoBuilderObject, - method_gnssAntennaInfoBuilderSetSignalGainCorrections, - signalGainCorrections); + callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject, + method_gnssAntennaInfoBuilderSetCarrierFrequencyMHz, + gnssAntennaInfo.carrierFrequencyMHz); + callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject, + method_gnssAntennaInfoBuilderSetPhaseCenterOffset, + phaseCenterOffset); + callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject, + method_gnssAntennaInfoBuilderSetPhaseCenterVariationCorrections, + phaseCenterVariationCorrections); + callObjectMethodIgnoringResult(env, gnssAntennaInfoBuilderObject, + method_gnssAntennaInfoBuilderSetSignalGainCorrections, + signalGainCorrections); // build jobject gnssAntennaInfoObject = diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp index fbdeec6b897e..34ca559806c6 100644 --- a/services/core/jni/gnss/GnssMeasurementCallback.cpp +++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp @@ -212,13 +212,14 @@ void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock, jobject gnssMeasurementsEventBuilderObject = env->NewObject(class_gnssMeasurementsEventBuilder, method_gnssMeasurementsEventBuilderCtor); - env->CallObjectMethod(gnssMeasurementsEventBuilderObject, - method_gnssMeasurementsEventBuilderSetClock, clock); - env->CallObjectMethod(gnssMeasurementsEventBuilderObject, - method_gnssMeasurementsEventBuilderSetMeasurements, measurementArray); - env->CallObjectMethod(gnssMeasurementsEventBuilderObject, - method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls, - gnssAgcArray); + callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject, + method_gnssMeasurementsEventBuilderSetClock, clock); + callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject, + method_gnssMeasurementsEventBuilderSetMeasurements, + measurementArray); + callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject, + method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls, + gnssAgcArray); jobject gnssMeasurementsEventObject = env->CallObjectMethod(gnssMeasurementsEventBuilderObject, method_gnssMeasurementsEventBuilderBuild); @@ -359,9 +360,7 @@ void GnssMeasurementCallbackAidl::translateAndSetGnssData(const GnssData& data) jobjectArray measurementArray = translateAllGnssMeasurements(env, data.measurements); jobjectArray gnssAgcArray = nullptr; - if (data.gnssAgcs.has_value()) { - gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs.value()); - } + gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs); setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray); env->DeleteLocalRef(clock); @@ -410,24 +409,24 @@ void GnssMeasurementCallbackAidl::translateSingleGnssMeasurement(JNIEnv* env, satellitePvt.satClockInfo.satHardwareCodeBiasMeters, satellitePvt.satClockInfo.satTimeCorrectionMeters, satellitePvt.satClockInfo.satClkDriftMps); - env->CallObjectMethod(satellitePvtBuilderObject, - method_satellitePvtBuilderSetPositionEcef, positionEcef); - env->CallObjectMethod(satellitePvtBuilderObject, - method_satellitePvtBuilderSetVelocityEcef, velocityEcef); - env->CallObjectMethod(satellitePvtBuilderObject, method_satellitePvtBuilderSetClockInfo, - clockInfo); + callObjectMethodIgnoringResult(env, satellitePvtBuilderObject, + method_satellitePvtBuilderSetPositionEcef, positionEcef); + callObjectMethodIgnoringResult(env, satellitePvtBuilderObject, + method_satellitePvtBuilderSetVelocityEcef, velocityEcef); + callObjectMethodIgnoringResult(env, satellitePvtBuilderObject, + method_satellitePvtBuilderSetClockInfo, clockInfo); } if (satFlags & SatellitePvt::HAS_IONO) { - env->CallObjectMethod(satellitePvtBuilderObject, - method_satellitePvtBuilderSetIonoDelayMeters, - satellitePvt.ionoDelayMeters); + callObjectMethodIgnoringResult(env, satellitePvtBuilderObject, + method_satellitePvtBuilderSetIonoDelayMeters, + satellitePvt.ionoDelayMeters); } if (satFlags & SatellitePvt::HAS_TROPO) { - env->CallObjectMethod(satellitePvtBuilderObject, - method_satellitePvtBuilderSetTropoDelayMeters, - satellitePvt.tropoDelayMeters); + callObjectMethodIgnoringResult(env, satellitePvtBuilderObject, + method_satellitePvtBuilderSetTropoDelayMeters, + satellitePvt.tropoDelayMeters); } jobject satellitePvtObject = @@ -455,17 +454,19 @@ void GnssMeasurementCallbackAidl::translateSingleGnssMeasurement(JNIEnv* env, jobject correlationVectorBuilderObject = env->NewObject(class_correlationVectorBuilder, method_correlationVectorBuilderCtor); - env->CallObjectMethod(correlationVectorBuilderObject, - method_correlationVectorBuilderSetMagnitude, magnitudeArray); - env->CallObjectMethod(correlationVectorBuilderObject, - method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond, - correlationVector.frequencyOffsetMps); - env->CallObjectMethod(correlationVectorBuilderObject, - method_correlationVectorBuilderSetSamplingStartMeters, - correlationVector.samplingStartM); - env->CallObjectMethod(correlationVectorBuilderObject, - method_correlationVectorBuilderSetSamplingWidthMeters, - correlationVector.samplingWidthM); + callObjectMethodIgnoringResult(env, correlationVectorBuilderObject, + method_correlationVectorBuilderSetMagnitude, + magnitudeArray); + callObjectMethodIgnoringResult( + env, correlationVectorBuilderObject, + method_correlationVectorBuilderSetFrequencyOffsetMetersPerSecond, + correlationVector.frequencyOffsetMps); + callObjectMethodIgnoringResult(env, correlationVectorBuilderObject, + method_correlationVectorBuilderSetSamplingStartMeters, + correlationVector.samplingStartM); + callObjectMethodIgnoringResult(env, correlationVectorBuilderObject, + method_correlationVectorBuilderSetSamplingWidthMeters, + correlationVector.samplingWidthM); jobject correlationVectorObject = env->CallObjectMethod(correlationVectorBuilderObject, method_correlationVectorBuilderBuild); @@ -508,8 +509,8 @@ jobjectArray GnssMeasurementCallbackAidl::translateAllGnssMeasurements( return gnssMeasurementArray; } -jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs( - JNIEnv* env, const std::vector<std::optional<GnssAgc>>& agcs) { +jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs(JNIEnv* env, + const std::vector<GnssAgc>& agcs) { if (agcs.size() == 0) { return nullptr; } @@ -518,18 +519,17 @@ jobjectArray GnssMeasurementCallbackAidl::translateAllGnssAgcs( env->NewObjectArray(agcs.size(), class_gnssAgc, nullptr /* initialElement */); for (uint16_t i = 0; i < agcs.size(); ++i) { - if (!agcs[i].has_value()) { - continue; - } - const GnssAgc& gnssAgc = agcs[i].value(); + const GnssAgc& gnssAgc = agcs[i]; jobject agcBuilderObject = env->NewObject(class_gnssAgcBuilder, method_gnssAgcBuilderCtor); - env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetLevelDb, - gnssAgc.agcLevelDb); - env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetConstellationType, - (int)gnssAgc.constellation); - env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderSetCarrierFrequencyHz, - gnssAgc.carrierFrequencyHz); + callObjectMethodIgnoringResult(env, agcBuilderObject, method_gnssAgcBuilderSetLevelDb, + gnssAgc.agcLevelDb); + callObjectMethodIgnoringResult(env, agcBuilderObject, + method_gnssAgcBuilderSetConstellationType, + (int)gnssAgc.constellation); + callObjectMethodIgnoringResult(env, agcBuilderObject, + method_gnssAgcBuilderSetCarrierFrequencyHz, + gnssAgc.carrierFrequencyHz); jobject agcObject = env->CallObjectMethod(agcBuilderObject, method_gnssAgcBuilderBuild); env->SetObjectArrayElement(gnssAgcArray, i, agcObject); diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h index 9b346312db38..17af94939666 100644 --- a/services/core/jni/gnss/GnssMeasurementCallback.h +++ b/services/core/jni/gnss/GnssMeasurementCallback.h @@ -62,8 +62,8 @@ private: jobjectArray translateAllGnssMeasurements( JNIEnv* env, const std::vector<hardware::gnss::GnssMeasurement>& measurements); - jobjectArray translateAllGnssAgcs( - JNIEnv* env, const std::vector<std::optional<hardware::gnss::GnssData::GnssAgc>>& agcs); + jobjectArray translateAllGnssAgcs(JNIEnv* env, + const std::vector<hardware::gnss::GnssData::GnssAgc>& agcs); void translateAndSetGnssData(const hardware::gnss::GnssData& data); diff --git a/services/core/jni/gnss/Utils.cpp b/services/core/jni/gnss/Utils.cpp index 40a94ce62b05..8f32c47fcb5a 100644 --- a/services/core/jni/gnss/Utils.cpp +++ b/services/core/jni/gnss/Utils.cpp @@ -111,6 +111,13 @@ void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { } } +void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...) { + va_list args; + va_start(args, mid); + env->DeleteLocalRef(env->CallObjectMethodV(obj, mid, args)); + va_end(args); +} + JavaObject::JavaObject(JNIEnv* env, jclass clazz, jmethodID defaultCtor) : env_(env), clazz_(clazz) { object_ = env_->NewObject(clazz_, defaultCtor); diff --git a/services/core/jni/gnss/Utils.h b/services/core/jni/gnss/Utils.h index 2640a7774c26..c8ee661bc10a 100644 --- a/services/core/jni/gnss/Utils.h +++ b/services/core/jni/gnss/Utils.h @@ -56,6 +56,8 @@ jboolean checkAidlStatus(const android::binder::Status& status, const char* erro void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); +void callObjectMethodIgnoringResult(JNIEnv* env, jobject obj, jmethodID mid, ...); + template <class T> void logHidlError(hardware::Return<T>& result, const char* errorMessage) { ALOGE("%s HIDL transport error: %s", errorMessage, result.description().c_str()); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 733cfcdfaea5..e34178ab9cd2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -56,6 +56,8 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING; import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS; import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT; import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING; +import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; +import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_ID; import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE_DRAWABLE; @@ -66,9 +68,12 @@ import static android.app.admin.DevicePolicyManager.ID_TYPE_INDIVIDUAL_ATTESTATI import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID; import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL; import static android.app.admin.DevicePolicyManager.LEAVE_ALL_SYSTEM_APPS_ENABLED; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO; import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY; import static android.app.admin.DevicePolicyManager.NON_ORG_OWNED_PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; import static android.app.admin.DevicePolicyManager.OPERATION_SAFETY_REASON_NONE; @@ -3918,8 +3923,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization( - isProfileOwner(caller) || isDeviceOwner(caller) || isSystemUid(caller) - || isPasswordLimitingAdminTargetingP(caller)); + isProfileOwner(caller) || isDefaultDeviceOwner(caller) + || isSystemUid(caller) || isPasswordLimitingAdminTargetingP(caller)); if (parent) { Preconditions.checkCallAuthorization( @@ -4772,7 +4777,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isProfileOwner(caller)); Preconditions.checkCallingUser(isManagedProfile(caller.getUserId())); return !isSeparateProfileChallengeEnabled(caller.getUserId()); @@ -4860,12 +4866,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { enforceUserUnlocked(caller.getUserId()); if (parent) { Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller), + isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller), "Only profile owner, device owner and system may call this method on parent."); } else { Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY) - || isDeviceOwner(caller) || isProfileOwner(caller), + || isDefaultDeviceOwner(caller) || isProfileOwner(caller), "Must have " + REQUEST_PASSWORD_COMPLEXITY + " permission, or be a profile owner or device owner."); } @@ -4888,7 +4894,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { "Provided complexity is not one of the allowed values."); final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller)); synchronized (getLockObject()) { @@ -4968,7 +4975,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwner(caller)); + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller)); @@ -5160,7 +5167,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } // If caller has PO (or DO) throw or fail silently depending on its target SDK level. - if (isDeviceOwner(caller) || isProfileOwner(caller)) { + if (isDefaultDeviceOwner(caller) || isProfileOwner(caller)) { synchronized (getLockObject()) { ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); if (getTargetSdk(admin.info.getPackageName(), userHandle) < Build.VERSION_CODES.O) { @@ -5219,7 +5226,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } - boolean callerIsDeviceOwnerAdmin = isDeviceOwner(caller); + boolean callerIsDeviceOwnerAdmin = isDefaultDeviceOwner(caller); boolean doNotAskCredentialsOnBoot = (flags & DevicePolicyManager.RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT) != 0; if (callerIsDeviceOwnerAdmin && doNotAskCredentialsOnBoot) { @@ -5406,7 +5413,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number."); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); // timeoutMs with value 0 means that the admin doesn't participate // timeoutMs is clamped to the interval in case the internal constants change in the future final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs(); @@ -5575,7 +5583,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private boolean canManageCaCerts(CallerIdentity caller) { - return (caller.hasAdminComponent() && (isDeviceOwner(caller) || isProfileOwner(caller))) + return (caller.hasAdminComponent() && (isDefaultDeviceOwner(caller) + || isProfileOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_INSTALL)) || hasCallingOrSelfPermission(MANAGE_CA_CERTIFICATES); } @@ -5689,7 +5698,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization(!isUserSelectable, "The credential " @@ -5754,7 +5763,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( @@ -5818,12 +5827,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private boolean canInstallCertificates(CallerIdentity caller) { - return isProfileOwner(caller) || isDeviceOwner(caller) + return isProfileOwner(caller) || isDefaultDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL); } private boolean canChooseCertificates(CallerIdentity caller) { - return isProfileOwner(caller) || isDeviceOwner(caller) + return isProfileOwner(caller) || isDefaultDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_SELECTION); } @@ -5871,7 +5880,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_CERT_SELECTION))); final int granteeUid; @@ -5984,7 +5993,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // If not, fall back to the device owner check. Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + isDefaultDeviceOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); } @VisibleForTesting @@ -6047,7 +6056,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags); } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( @@ -6182,7 +6191,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( @@ -6337,14 +6346,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userId = caller.getUserId(); // Ensure calling process is device/profile owner. if (!Collections.disjoint(scopes, DEVICE_OWNER_OR_MANAGED_PROFILE_OWNER_DELEGATIONS)) { - Preconditions.checkCallAuthorization(isDeviceOwner(caller) + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || (isProfileOwner(caller) && isManagedProfile(caller.getUserId()))); } else if (!Collections.disjoint( scopes, DEVICE_OWNER_OR_ORGANIZATION_OWNED_MANAGED_PROFILE_OWNER_DELEGATIONS)) { - Preconditions.checkCallAuthorization(isDeviceOwner(caller) + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); } else { - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); } synchronized (getLockObject()) { @@ -6434,7 +6444,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // * Either it's a profile owner / device owner, if componentName is provided // * Or it's an app querying its own delegation scopes if (caller.hasAdminComponent()) { - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); } else { Preconditions.checkCallAuthorization(isPackage(caller, delegatePackage), String.format("Caller with uid %d is not %s", caller.getUid(), @@ -6467,7 +6478,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Retrieve the user ID of the calling process. final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); synchronized (getLockObject()) { return getDelegatePackagesInternalLocked(scope, caller.getUserId()); } @@ -6600,7 +6612,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(who); // Ensure calling process is device/profile owner. - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { final DevicePolicyData policy = getUserData(caller.getUserId()); @@ -6712,7 +6725,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_ALWAYS_ON_VPN_PACKAGE); if (vpnPackage == null) { @@ -6792,7 +6806,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); return mInjector.binderWithCleanCallingIdentity( () -> mInjector.getVpnManager().getAlwaysOnVpnPackageForUser(caller.getUserId())); @@ -6818,7 +6833,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { caller = getCallerIdentity(); } else { caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); } return mInjector.binderWithCleanCallingIdentity( @@ -6841,7 +6857,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); return mInjector.binderWithCleanCallingIdentity( () -> mInjector.getVpnManager().getVpnLockdownAllowlist(caller.getUserId())); @@ -6946,8 +6963,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { + "organization-owned device."); } if ((flags & WIPE_RESET_PROTECTION_DATA) != 0) { - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || calledByProfileOwnerOnOrgOwnedDevice, + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || calledByProfileOwnerOnOrgOwnedDevice + || isFinancedDeviceOwner(caller), "Only device owners or profile owners of organization-owned device can set " + "WIPE_RESET_PROTECTION_DATA"); } @@ -7139,8 +7157,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkNotNull(who, "ComponentName is null"); CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager .OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY); @@ -7189,7 +7207,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { UserHandle.getUserId(frpManagementAgentUid)); } else { Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); admin = getProfileOwnerOrDeviceOwnerLocked(caller); } } @@ -7617,7 +7636,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setRecommendedGlobalProxy(ComponentName who, ProxyInfo proxyInfo) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkAllUsersAreAffiliatedWithDevice(); mInjector.binderWithCleanCallingIdentity( () -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo)); @@ -7915,7 +7934,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); synchronized (getLockObject()) { final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); @@ -7934,8 +7954,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - isProfileOwner(caller) - || isDeviceOwner(caller) + isProfileOwner(caller) || isDefaultDeviceOwner(caller) || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY)); synchronized (getLockObject()) { @@ -7955,7 +7974,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); synchronized (getLockObject()) { final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); @@ -7974,8 +7994,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - isProfileOwner(caller) - || isDeviceOwner(caller) + isProfileOwner(caller) || isDefaultDeviceOwner(caller) || hasCallingOrSelfPermission(permission.READ_NEARBY_STREAMING_POLICY)); synchronized (getLockObject()) { @@ -8067,7 +8086,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller)); + || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller)); mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0)); @@ -8091,7 +8110,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller)); + || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller)); return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0; } @@ -8108,7 +8127,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller)); + || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller)); mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0)); @@ -8132,7 +8151,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDeviceOwner(caller)); + || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller)); return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0; } @@ -8161,7 +8180,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // which could still contain data related to that user. Should we disallow that, e.g. until // next boot? Might not be needed given that this still requires user consent. final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkAllUsersAreAffiliatedWithDevice(); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REQUEST_BUGREPORT); @@ -8472,7 +8491,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(packageList, "packageList is null"); final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && isDefaultDeviceOwner(caller)) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES))); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_KEEP_UNINSTALLED_PACKAGES); @@ -8501,7 +8521,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } final CallerIdentity caller = getCallerIdentity(who, callerPackage); - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && isDeviceOwner(caller)) + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && isDefaultDeviceOwner(caller)) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_KEEP_UNINSTALLED_PACKAGES))); @@ -8615,8 +8636,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean hasDeviceOwner() { final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || canManageUsers(caller) + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || canManageUsers(caller) || isFinancedDeviceOwner(caller) || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); return mOwners.hasDeviceOwner(); } @@ -8633,17 +8654,29 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private boolean isDeviceOwner(CallerIdentity caller) { + /** + * Returns {@code true} <b>only if</b> the caller is the device owner and the device owner type + * is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}. {@code false} is returned for the + * case where the caller is not the device owner, there is no device owner, or the device owner + * type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_DEFAULT}. + * + */ + private boolean isDefaultDeviceOwner(CallerIdentity caller) { synchronized (getLockObject()) { - if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) { - return false; - } + return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked( + mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_DEFAULT; + } + } - if (caller.hasAdminComponent()) { - return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName()); - } else { - return isUidDeviceOwnerLocked(caller.getUid()); - } + private boolean isDeviceOwnerLocked(CallerIdentity caller) { + if (!mOwners.hasDeviceOwner() || mOwners.getDeviceOwnerUserId() != caller.getUserId()) { + return false; + } + + if (caller.hasAdminComponent()) { + return mOwners.getDeviceOwnerComponent().equals(caller.getComponentName()); + } else { + return isUidDeviceOwnerLocked(caller.getUid()); } } @@ -9073,8 +9106,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); mInjector.binderWithCleanCallingIdentity(() -> mLockPatternUtils.setDeviceOwnerInfo(info != null ? info.toString() : null)); @@ -9258,7 +9291,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(who); final int userId = caller.getUserId(); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); Preconditions.checkCallingUser(isManagedProfile(userId)); synchronized (getLockObject()) { @@ -9289,7 +9323,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); mInjector.binderWithCleanCallingIdentity(() -> { mUserManager.setUserName(caller.getUserId(), profileName); @@ -9725,7 +9760,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private void enforceCanCallLockTaskLocked(CallerIdentity caller) { - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isProfileOwner(caller) + || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller)); final int userId = caller.getUserId(); if (!canUserUseLockTaskLocked(userId)) { @@ -9982,7 +10018,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { ComponentName activity) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isProfileOwner(caller) + || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller)); final int userHandle = caller.getUserId(); synchronized (getLockObject()) { @@ -10009,7 +10046,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void clearPackagePersistentPreferredActivities(ComponentName who, String packageName) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isProfileOwner(caller) + || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller)); final int userHandle = caller.getUserId(); synchronized (getLockObject()) { @@ -10030,7 +10068,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller))); if (parent) { mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage( @@ -10070,7 +10108,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { String packageName, Bundle settings) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS))); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS); @@ -10168,7 +10206,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setRestrictionsProvider(ComponentName who, ComponentName permissionProvider) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_RESTRICTIONS_PROVIDER); synchronized (getLockObject()) { @@ -10193,7 +10232,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void addCrossProfileIntentFilter(ComponentName who, IntentFilter filter, int flags) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); int callingUserId = caller.getUserId(); synchronized (getLockObject()) { long id = mInjector.binderClearCallingIdentity(); @@ -10242,7 +10282,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void clearCrossProfileIntentFilters(ComponentName who) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); int callingUserId = caller.getUserId(); synchronized (getLockObject()) { @@ -10382,7 +10423,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); synchronized (getLockObject()) { ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); @@ -10495,7 +10537,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { + "system input methods when called on the parent instance of an " + "organization-owned device"); } else { - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); } if (packageList != null) { @@ -10553,7 +10596,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (calledOnParentInstance) { Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller)); } else { - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); } synchronized (getLockObject()) { @@ -10732,7 +10776,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Only allow the system user to use this method Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(), "createAndManageUser was called from non-system user"); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER); final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0; @@ -10974,7 +11018,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(userHandle, "UserHandle is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REMOVE_USER); return mInjector.binderWithCleanCallingIdentity(() -> { @@ -11008,7 +11052,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean switchUser(ComponentName who, UserHandle userHandle) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SWITCH_USER); boolean switched = false; @@ -11083,7 +11127,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(userHandle, "UserHandle is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_START_USER_IN_BACKGROUND); final int userId = userHandle.getIdentifier(); @@ -11119,7 +11163,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(userHandle, "UserHandle is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_STOP_USER); final int userId = userHandle.getIdentifier(); @@ -11135,7 +11179,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public int logoutUser(ComponentName who) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_LOGOUT_USER); final int callingUserId = caller.getUserId(); @@ -11224,7 +11269,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public List<UserHandle> getSecondaryUsers(ComponentName who) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); return mInjector.binderWithCleanCallingIdentity(() -> { final List<UserInfo> userInfos = mInjector.getUserManager().getAliveUsers(); @@ -11244,7 +11289,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); return mInjector.binderWithCleanCallingIdentity( () -> mInjector.getUserManager().isUserEphemeral(caller.getUserId())); @@ -11255,7 +11301,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { String packageName) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS))); return mInjector.binderWithCleanCallingIdentity(() -> { @@ -11306,7 +11352,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(packageNames, "array of packages cannot be null"); final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS))); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED); @@ -11369,7 +11415,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean isPackageSuspended(ComponentName who, String callerPackage, String packageName) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS))); synchronized (getLockObject()) { @@ -11390,8 +11436,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public List<String> listPolicyExemptApps() { CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) || isDeviceOwner(caller) - || isProfileOwner(caller)); + hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) + || isDefaultDeviceOwner(caller) || isProfileOwner(caller)); return listPolicyExemptAppsUnchecked(); } @@ -11432,12 +11478,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final ActiveAdmin activeAdmin = getParentOfAdminIfRequired( getProfileOwnerOrDeviceOwnerLocked(caller), parent); - if (isDeviceOwner(caller)) { + if (isDefaultDeviceOwner(caller)) { if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) { throw new SecurityException("Device owner cannot set user restriction " + key); } Preconditions.checkArgument(!parent, "Cannot use the parent instance in Device Owner mode"); + } else if (isFinancedDeviceOwner(caller)) { + if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) { + throw new SecurityException("Cannot set user restriction " + key + + " when managing a financed device"); + } + Preconditions.checkArgument(!parent, + "Cannot use the parent instance in Financed Device Owner mode"); } else { boolean profileOwnerCanChangeOnItself = !parent && UserRestrictionsUtils.canProfileOwnerChange(key, userHandle); @@ -11546,7 +11599,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller) + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isProfileOwner(caller) || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller))); synchronized (getLockObject()) { @@ -11561,7 +11615,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { boolean hidden, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS))); List<String> exemptApps = listPolicyExemptAppsUnchecked(); @@ -11607,7 +11661,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { String packageName, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS))); final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId(); @@ -11643,7 +11697,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void enableSystemApp(ComponentName who, String callerPackage, String packageName) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP))); synchronized (getLockObject()) { @@ -11687,7 +11741,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public int enableSystemAppWithIntent(ComponentName who, String callerPackage, Intent intent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_ENABLE_SYSTEM_APP))); int numberOfAppsInstalled = 0; @@ -11756,7 +11810,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { String packageName) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_INSTALL_EXISTING_PACKAGE))); @@ -11872,7 +11926,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { boolean uninstallBlocked) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller) + || isFinancedDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_BLOCK_UNINSTALL))); final int userId = caller.getUserId(); @@ -11913,7 +11968,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (who != null) { final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization( - isProfileOwner(caller) || isDeviceOwner(caller)); + isProfileOwner(caller) || isDefaultDeviceOwner(caller) + || isFinancedDeviceOwner(caller)); } try { return mIPackageManager.getBlockUninstallForUser(packageName, userId); @@ -12087,7 +12143,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); synchronized (getLockObject()) { ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); @@ -12111,7 +12168,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); synchronized (getLockObject()) { ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); @@ -12136,7 +12194,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Check can set secondary lockscreen enabled final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); Preconditions.checkCallAuthorization(!isManagedProfile(caller.getUserId()), "User %d is not allowed to call setSecondaryLockscreenEnabled", caller.getUserId()); @@ -12325,6 +12384,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userHandle = caller.getUserId(); synchronized (getLockObject()) { enforceCanCallLockTaskLocked(caller); + enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES); setLockTaskFeaturesLocked(userHandle, flags); } @@ -12373,6 +12433,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { }); } + private void enforceCanSetLockTaskFeaturesOnFinancedDevice(CallerIdentity caller, int flags) { + int allowedFlags = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD + | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS + | LOCK_TASK_FEATURE_NOTIFICATIONS; + + if (!isFinancedDeviceOwner(caller)) { + return; + } + + if ((flags == 0) || ((flags & ~(allowedFlags)) != 0)) { + throw new SecurityException( + "Permitted lock task features when managing a financed device: " + + "LOCK_TASK_FEATURE_SYSTEM_INFO, LOCK_TASK_FEATURE_KEYGUARD, " + + "LOCK_TASK_FEATURE_HOME, LOCK_TASK_FEATURE_GLOBAL_ACTIONS, " + + "or LOCK_TASK_FEATURE_NOTIFICATIONS"); + } + } + @Override public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) { Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()), @@ -12413,7 +12491,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setGlobalSetting(ComponentName who, String setting, String value) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_GLOBAL_SETTING) @@ -12454,7 +12532,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkStringNotEmpty(setting, "String setting is null or empty"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_SETTING); synchronized (getLockObject()) { @@ -12476,8 +12555,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkNotNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, @@ -12498,8 +12577,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkNotNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); return mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0); @@ -12510,7 +12589,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkNotNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); UserHandle userHandle = caller.getUserHandle(); if (mIsAutomotive && !locationEnabled) { @@ -12592,8 +12671,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); // Don't allow set time when auto time is on. if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) { @@ -12612,8 +12691,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); // Don't allow set timezone when auto timezone is on. if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) { @@ -12633,7 +12712,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setSecureSetting(ComponentName who, String setting, String value) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); int callingUserId = caller.getUserId(); synchronized (getLockObject()) { @@ -12720,7 +12800,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setMasterVolumeMuted(ComponentName who, boolean on) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_MASTER_VOLUME_MUTED); synchronized (getLockObject()) { @@ -12737,7 +12818,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean isMasterVolumeMuted(ComponentName who) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { AudioManager audioManager = @@ -12750,7 +12832,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setUserIcon(ComponentName who, Bitmap icon) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { mInjector.binderWithCleanCallingIdentity( @@ -12766,7 +12849,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public boolean setKeyguardDisabled(ComponentName who, boolean disabled) { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); final int userId = caller.getUserId(); synchronized (getLockObject()) { @@ -12808,7 +12892,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean setStatusBarDisabled(ComponentName who, boolean disabled) { final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); int userId = caller.getUserId(); synchronized (getLockObject()) { @@ -13042,7 +13127,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isActiveDeviceOwner(int uid) { - return isDeviceOwner(new CallerIdentity(uid, null, null)); + return isDefaultDeviceOwner(new CallerIdentity(uid, null, null)); } @Override @@ -13633,7 +13718,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDeviceOwner(caller)); + || isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY); if (policy == null) { @@ -13831,7 +13916,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); return mOwners.getSystemUpdateInfo(); } @@ -13840,7 +13926,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setPermissionPolicy(ComponentName admin, String callerPackage, int policy) { final CallerIdentity caller = getCallerIdentity(admin, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT))); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_POLICY); @@ -13882,11 +13968,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(admin, callerPackage); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller) + || isFinancedDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT))); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PERMISSION_GRANT_STATE); synchronized (getLockObject()) { + if (isFinancedDeviceOwner(caller)) { + enforceCanSetPermissionGrantOnFinancedDevice(packageName, permission); + } long ident = mInjector.binderClearCallingIdentity(); try { boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) @@ -13946,12 +14036,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private void enforceCanSetPermissionGrantOnFinancedDevice( + String packageName, String permission) { + if (!Manifest.permission.READ_PHONE_STATE.equals(permission)) { + throw new SecurityException("Cannot grant " + permission + + " when managing a financed device"); + } else if (!mOwners.getDeviceOwnerPackageName().equals(packageName)) { + throw new SecurityException("Cannot grant permission to a package that is not" + + " the device owner"); + } + } + @Override public int getPermissionGrantState(ComponentName admin, String callerPackage, String packageName, String permission) throws RemoteException { final CallerIdentity caller = getCallerIdentity(admin, callerPackage); Preconditions.checkCallAuthorization(isSystemUid(caller) || (caller.hasAdminComponent() - && (isProfileOwner(caller) || isDeviceOwner(caller))) + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PERMISSION_GRANT))); synchronized (getLockObject()) { @@ -14231,7 +14332,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private void checkIsDeviceOwner(CallerIdentity caller) { - Preconditions.checkCallAuthorization(isDeviceOwner(caller), caller.getUid() + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller), caller.getUid() + " is not device owner"); } @@ -14263,8 +14364,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); return mInjector.binderWithCleanCallingIdentity(() -> { String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses(); @@ -14299,7 +14400,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); return isManagedProfile(caller.getUserId()); } @@ -14308,7 +14410,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void reboot(ComponentName admin) { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_REBOOT); mInjector.binderWithCleanCallingIdentity(() -> { // Make sure there are no ongoing calls on the device. @@ -14542,7 +14644,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return null; } final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || canManageUsers(caller) || isFinancedDeviceOwner(caller)); synchronized (getLockObject()) { final ActiveAdmin deviceOwnerAdmin = getDeviceOwnerAdminLocked(); return deviceOwnerAdmin == null ? null : deviceOwnerAdmin.organizationName; @@ -14576,7 +14679,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who); Objects.requireNonNull(packageNames); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller), + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller), "Admin %s does not own the profile", caller.getComponentName()); if (!mHasFeature) { @@ -14627,7 +14731,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return new ArrayList<>(); } final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller), + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller), "Admin %s does not own the profile", caller.getComponentName()); synchronized (getLockObject()) { @@ -14766,7 +14871,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final Set<String> affiliationIds = new ArraySet<>(ids); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); final int callingUserId = caller.getUserId(); synchronized (getLockObject()) { @@ -14797,7 +14903,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { return new ArrayList<String>(getUserData(caller.getUserId()).mAffiliationIds); @@ -14891,7 +14998,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (admin != null) { Preconditions.checkCallAuthorization( isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDeviceOwner(caller)); + || isDefaultDeviceOwner(caller)); } else { // A delegate app passes a null admin component, which is expected Preconditions.checkCallAuthorization( @@ -14928,7 +15035,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (admin != null) { Preconditions.checkCallAuthorization( isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDeviceOwner(caller)); + || isDefaultDeviceOwner(caller)); } else { // A delegate app passes a null admin component, which is expected Preconditions.checkCallAuthorization( @@ -14961,7 +15068,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (admin != null) { Preconditions.checkCallAuthorization( isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDeviceOwner(caller)); + || isDefaultDeviceOwner(caller)); } else { // A delegate app passes a null admin component, which is expected Preconditions.checkCallAuthorization( @@ -15007,7 +15114,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (admin != null) { Preconditions.checkCallAuthorization( isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDeviceOwner(caller)); + || isDefaultDeviceOwner(caller)); } else { // A delegate app passes a null admin component, which is expected Preconditions.checkCallAuthorization( @@ -15246,7 +15353,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isProfileOwner(caller) || isFinancedDeviceOwner(caller)); toggleBackupServiceActive(caller.getUserId(), enabled); } @@ -15259,7 +15367,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isProfileOwner(caller) || isFinancedDeviceOwner(caller)); return mInjector.binderWithCleanCallingIdentity(() -> { synchronized (getLockObject()) { @@ -15334,7 +15443,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(admin); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { final int callingUserId = caller.getUserId(); @@ -15462,7 +15572,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean isManagedProfileOwner = isProfileOwner(caller) && isManagedProfile(caller.getUserId()); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isDeviceOwner(caller) || isManagedProfileOwner)) + && (isDefaultDeviceOwner(caller) || isManagedProfileOwner)) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))); synchronized (getLockObject()) { @@ -15622,7 +15732,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final CallerIdentity caller = getCallerIdentity(admin, packageName); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isDeviceOwner(caller) + && (isDefaultDeviceOwner(caller) || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())))) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING)) || hasCallingOrSelfPermission(permission.MANAGE_USERS)); @@ -15654,7 +15764,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final boolean isManagedProfileOwner = isProfileOwner(caller) && isManagedProfile(caller.getUserId()); Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isDeviceOwner(caller) || isManagedProfileOwner)) + && (isDefaultDeviceOwner(caller) || isManagedProfileOwner)) || (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_NETWORK_LOGGING))); if (mOwners.hasDeviceOwner()) { checkAllUsersAreAffiliatedWithDevice(); @@ -15815,14 +15925,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public long getLastSecurityLogRetrievalTime() { final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || canManageUsers(caller)); return getUserData(UserHandle.USER_SYSTEM).mLastSecurityLogRetrievalTime; } @Override public long getLastBugReportRequestTime() { final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || canManageUsers(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || canManageUsers(caller)); return getUserData(UserHandle.USER_SYSTEM).mLastBugReportRequestTime; } @@ -15830,7 +15942,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public long getLastNetworkLogRetrievalTime() { final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || (isProfileOwner(caller) && isManagedProfile(caller.getUserId())) || canManageUsers(caller)); final int affectedUserId = getNetworkLoggingAffectedUser(); @@ -15846,7 +15958,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new IllegalArgumentException("token must be at least 32-byte long"); } final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { final int userHandle = caller.getUserId(); @@ -15870,7 +15983,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { final int userHandle = caller.getUserId(); @@ -15895,7 +16009,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { return isResetPasswordTokenActiveForUserLocked(caller.getUserId()); @@ -15920,7 +16035,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(token); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { DevicePolicyData policy = getUserData(caller.getUserId()); @@ -15945,7 +16061,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public boolean isCurrentInputMethodSetByOwner() { final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller), "Only profile owner, device owner and system may call this method."); return getUserData(caller.getUserId()).mCurrentInputMethodSet; @@ -15956,7 +16072,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userId = user.getIdentifier(); final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization((userId == caller.getUserId()) - || isProfileOwner(caller) || isDeviceOwner(caller) + || isProfileOwner(caller) || isDefaultDeviceOwner(caller) || hasFullCrossUsersPermission(caller, userId)); synchronized (getLockObject()) { @@ -15973,7 +16089,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(callback, "callback is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CLEAR_APPLICATION_USER_DATA); long ident = mInjector.binderClearCallingIdentity(); @@ -16005,7 +16122,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOGOUT_ENABLED); synchronized (getLockObject()) { @@ -16054,7 +16171,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { "Provided administrator and target have the same package name."); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); final int callingUserId = caller.getUserId(); final DevicePolicyData policy = getUserData(callingUserId); @@ -16096,7 +16214,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (isUserAffiliatedWithDeviceLocked(callingUserId)) { notifyAffiliatedProfileTransferOwnershipComplete(callingUserId); } - } else if (isDeviceOwner(caller)) { + } else if (isDefaultDeviceOwner(caller)) { ownerType = ADMIN_TYPE_DEVICE_OWNER; prepareTransfer(admin, target, bundle, callingUserId, ADMIN_TYPE_DEVICE_OWNER); @@ -16177,7 +16295,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); final String startUserSessionMessageString = startUserSessionMessage != null ? startUserSessionMessage.toString() : null; @@ -16202,7 +16320,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); final String endUserSessionMessageString = endUserSessionMessage != null ? endUserSessionMessage.toString() : null; @@ -16227,7 +16345,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); @@ -16242,7 +16360,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); @@ -16258,7 +16376,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Nullable public PersistableBundle getTransferOwnershipBundle() { final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isProfileOwner(caller) || isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { final int callingUserId = caller.getUserId(); @@ -16288,7 +16407,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(apnSetting, "ApnSetting is null in addOverrideApn"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); if (tm != null) { @@ -16309,7 +16428,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(apnSetting, "ApnSetting is null in updateOverrideApn"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); if (apnId < 0) { return false; @@ -16331,7 +16450,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); return removeOverrideApnUnchecked(apnId); } @@ -16352,7 +16471,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); return getOverrideApnsUnchecked(); } @@ -16373,7 +16492,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_OVERRIDE_APNS_ENABLED); setOverrideApnsEnabledUnchecked(enabled); @@ -16393,7 +16512,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); Cursor enforceCursor = mInjector.binderWithCleanCallingIdentity( () -> mContext.getContentResolver().query( @@ -16476,7 +16595,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); checkAllUsersAreAffiliatedWithDevice(); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS); @@ -16515,7 +16634,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); final int currentMode = ConnectivitySettingsManager.getPrivateDnsMode(mContext); switch (currentMode) { @@ -16537,7 +16656,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); return mInjector.settingsGlobalGetString(PRIVATE_DNS_SPECIFIER); } @@ -16547,8 +16666,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE); DevicePolicyEventLogger @@ -16923,7 +17042,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); Objects.requireNonNull(packages, "packages is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller)); checkCanExecuteOrThrowUnsafe( DevicePolicyManager.OPERATION_SET_USER_CONTROL_DISABLED_PACKAGES); @@ -16942,7 +17062,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization(isDeviceOwner(caller)); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller)); synchronized (getLockObject()) { return mOwners.getDeviceOwnerProtectedPackages(who.getPackageName()); @@ -16954,7 +17075,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(who, "Admin component name must be provided"); final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), "Common Criteria mode can only be controlled by a device owner or " + "a profile owner on an organization-owned device."); synchronized (getLockObject()) { @@ -16974,7 +17095,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (who != null) { final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), "Common Criteria mode can only be controlled by a device owner or " + "a profile owner on an organization-owned device."); @@ -17446,7 +17567,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(callerPackage); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwner(caller) + isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); synchronized (getLockObject()) { @@ -17467,7 +17588,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(callerPackage); // Only the DPC can set this ID. - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller), + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) || isProfileOwner(caller), "Only a Device Owner or Profile Owner may set the Enterprise ID."); // Empty enterprise ID must not be provided in calls to this method. Preconditions.checkArgument(!TextUtils.isEmpty(organizationId), @@ -17559,6 +17680,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { ? Collections.emptySet() : mOverlayPackagesProvider.getNonRequiredApps( admin, caller.getUserId(), ACTION_PROVISION_MANAGED_PROFILE); + if (nonRequiredApps.isEmpty()) { + Slogf.i(LOG_TAG, "No disallowed packages for the managed profile."); + } else { + for (String packageName : nonRequiredApps) { + Slogf.i(LOG_TAG, "Disallowed package [" + packageName + "]"); + } + } userInfo = mUserManager.createProfileForUserEvenWhenDisallowed( provisioningParams.getProfileName(), UserManager.USER_TYPE_PROFILE_MANAGED, @@ -17577,6 +17705,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { startTime, callerPackage); + onCreateAndProvisionManagedProfileStarted(provisioningParams); + installExistingAdminPackage(userInfo.id, admin.getPackageName()); if (!enableAdminAndSetProfileOwner( userInfo.id, caller.getUserId(), admin, provisioningParams.getOwnerName())) { @@ -17597,6 +17727,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + onCreateAndProvisionManagedProfileCompleted(provisioningParams); + sendProvisioningCompletedBroadcast( userInfo.id, ACTION_PROVISION_MANAGED_PROFILE, @@ -17618,6 +17750,29 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + /** + * Callback called at the beginning of {@link #createAndProvisionManagedProfile( + * ManagedProfileProvisioningParams, String)} after the relevant prechecks have passed. + * + * <p>The logic in this method blocks provisioning. + * + * <p>This method is meant to be overridden by OEMs. + */ + private void onCreateAndProvisionManagedProfileStarted( + ManagedProfileProvisioningParams provisioningParams) {} + + /** + * Callback called at the end of {@link #createAndProvisionManagedProfile( + * ManagedProfileProvisioningParams, String)} after all the other provisioning tasks + * have completed successfully. + * + * <p>The logic in this method blocks provisioning. + * + * <p>This method is meant to be overridden by OEMs. + */ + private void onCreateAndProvisionManagedProfileCompleted( + ManagedProfileProvisioningParams provisioningParams) {} + private void resetInteractAcrossProfilesAppOps() { mInjector.getCrossProfileApps().clearInteractAcrossProfilesAppOps(); pregrantDefaultInteractAcrossProfilesAppOps(); @@ -17857,6 +18012,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { ERROR_PRE_CONDITION_FAILED, "Provisioning preconditions failed with result: " + result); } + onProvisionFullyManagedDeviceStarted(provisioningParams); setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime()); setLocale(provisioningParams.getLocale()); @@ -17882,6 +18038,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { disallowAddUser(); setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId, provisioningParams.canDeviceOwnerGrantSensorsPermissions()); + onProvisionFullyManagedDeviceCompleted(provisioningParams); sendProvisioningCompletedBroadcast( deviceOwnerUserId, ACTION_PROVISION_MANAGED_DEVICE, @@ -17897,6 +18054,29 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + /** + * Callback called at the beginning of {@link #provisionFullyManagedDevice( + * FullyManagedDeviceProvisioningParams, String)} after the relevant prechecks have passed. + * + * <p>The logic in this method blocks provisioning. + * + * <p>This method is meant to be overridden by OEMs. + */ + private void onProvisionFullyManagedDeviceStarted( + FullyManagedDeviceProvisioningParams provisioningParams) {} + + /** + * Callback called at the end of {@link #provisionFullyManagedDevice( + * FullyManagedDeviceProvisioningParams, String)} after all the other provisioning tasks + * have completed successfully. + * + * <p>The logic in this method blocks provisioning. + * + * <p>This method is meant to be overridden by OEMs. + */ + private void onProvisionFullyManagedDeviceCompleted( + FullyManagedDeviceProvisioningParams provisioningParams) {} + private void setTimeAndTimezone(String timeZone, long localTime) { try { final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); @@ -18141,27 +18321,55 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @DeviceOwnerType int deviceOwnerType) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); - verifyDeviceOwnerTypePreconditions(admin); - - final String packageName = admin.getPackageName(); - Preconditions.checkState(!mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName), - "The device owner type has already been set for " + packageName); synchronized (getLockObject()) { - mOwners.setDeviceOwnerType(packageName, deviceOwnerType); + setDeviceOwnerTypeLocked(admin, deviceOwnerType); } } + private void setDeviceOwnerTypeLocked(ComponentName admin, + @DeviceOwnerType int deviceOwnerType) { + String packageName = admin.getPackageName(); + boolean isAdminTestOnly; + + verifyDeviceOwnerTypePreconditionsLocked(admin); + + isAdminTestOnly = isAdminTestOnlyLocked(admin, mOwners.getDeviceOwnerUserId()); + Preconditions.checkState(isAdminTestOnly + || !mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName), + "Test only admins can only set the device owner type more than once"); + + mOwners.setDeviceOwnerType(packageName, deviceOwnerType, isAdminTestOnly); + } + @Override @DeviceOwnerType public int getDeviceOwnerType(@NonNull ComponentName admin) { - verifyDeviceOwnerTypePreconditions(admin); synchronized (getLockObject()) { - return mOwners.getDeviceOwnerType(admin.getPackageName()); + verifyDeviceOwnerTypePreconditionsLocked(admin); + return getDeviceOwnerTypeLocked(admin.getPackageName()); + } + } + + @DeviceOwnerType + private int getDeviceOwnerTypeLocked(String packageName) { + return mOwners.getDeviceOwnerType(packageName); + } + + /** + * {@code true} is returned <b>only if</b> the caller is the device owner and the device owner + * type is {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}. {@code false} is returned for + * the case where the caller is not the device owner, there is no device owner, or the device + * owner type is not {@link DevicePolicyManager#DEVICE_OWNER_TYPE_FINANCED}. + */ + private boolean isFinancedDeviceOwner(CallerIdentity caller) { + synchronized (getLockObject()) { + return isDeviceOwnerLocked(caller) && getDeviceOwnerTypeLocked( + mOwners.getDeviceOwnerPackageName()) == DEVICE_OWNER_TYPE_FINANCED; } } - private void verifyDeviceOwnerTypePreconditions(@NonNull ComponentName admin) { + private void verifyDeviceOwnerTypePreconditionsLocked(@NonNull ComponentName admin) { Preconditions.checkState(mOwners.hasDeviceOwner(), "there is no device owner"); Preconditions.checkState(mOwners.getDeviceOwnerComponent().equals(admin), "admin is not the device owner"); @@ -18172,7 +18380,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Objects.requireNonNull(packageName, "Admin package name must be provided"); final CallerIdentity caller = getCallerIdentity(packageName); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), "USB data signaling can only be controlled by a device owner or " + "a profile owner on an organization-owned device."); Preconditions.checkState(canUsbDataSignalingBeDisabled(), @@ -18213,7 +18421,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { // If the caller is an admin, return the policy set by itself. Otherwise // return the device-wide policy. - if (isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) { + if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) { return getProfileOwnerOrDeviceOwnerLocked(caller).mUsbDataSignalingEnabled; } else { return isUsbDataSignalingEnabledInternalLocked(); @@ -18254,7 +18462,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setMinimumRequiredWifiSecurityLevel(int level) { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), "Wi-Fi minimum security level can only be controlled by a device owner or " + "a profile owner on an organization-owned device."); @@ -18284,7 +18492,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setSsidAllowlist(List<String> ssids) { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), "SSID allowlist can only be controlled by a device owner or " + "a profile owner on an organization-owned device."); @@ -18306,10 +18514,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public List<String> getSsidAllowlist() { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) - || isSystemUid(caller), + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) + || canQueryAdminPolicy(caller), "SSID allowlist can only be retrieved by a device owner or " - + "a profile owner on an organization-owned device or a system app."); + + "a profile owner on an organization-owned device or " + + "an app with the QUERY_ADMIN_POLICY permission."); synchronized (getLockObject()) { final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( UserHandle.USER_SYSTEM); @@ -18322,7 +18531,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public void setSsidDenylist(List<String> ssids) { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), "SSID denylist can only be controlled by a device owner or " + "a profile owner on an organization-owned device."); @@ -18344,10 +18553,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public List<String> getSsidDenylist() { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) - || isSystemUid(caller), + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) + || canQueryAdminPolicy(caller), "SSID denylist can only be retrieved by a device owner or " - + "a profile owner on an organization-owned device or a system app."); + + "a profile owner on an organization-owned device or " + + "an app with the QUERY_ADMIN_POLICY permission."); synchronized (getLockObject()) { final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( UserHandle.USER_SYSTEM); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java index 685cf0580a48..598f9e88ac6d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java @@ -34,6 +34,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; +import android.app.role.RoleManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -41,6 +42,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.os.Binder; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.view.inputmethod.InputMethodInfo; @@ -91,6 +93,8 @@ public class OverlayPackagesProvider { List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId); String getActiveApexPackageNameContainingPackage(String packageName); + + String getDeviceManagerRoleHolderPackageName(Context context); } private static final class DefaultInjector implements Injector { @@ -104,6 +108,19 @@ public class OverlayPackagesProvider { public String getActiveApexPackageNameContainingPackage(String packageName) { return ApexManager.getInstance().getActiveApexPackageNameContainingPackage(packageName); } + + @Override + public String getDeviceManagerRoleHolderPackageName(Context context) { + return Binder.withCleanCallingIdentity(() -> { + RoleManager roleManager = context.getSystemService(RoleManager.class); + List<String> roleHolders = + roleManager.getRoleHolders(RoleManager.ROLE_DEVICE_MANAGER); + if (roleHolders.isEmpty()) { + return null; + } + return roleHolders.get(0); + }); + } } @VisibleForTesting @@ -142,9 +159,20 @@ public class OverlayPackagesProvider { nonRequiredApps.addAll(getDisallowedApps(provisioningAction)); nonRequiredApps.removeAll( getRequiredAppsMainlineModules(nonRequiredApps, provisioningAction)); + nonRequiredApps.removeAll(getDeviceManagerRoleHolders()); return nonRequiredApps; } + private Set<String> getDeviceManagerRoleHolders() { + HashSet<String> result = new HashSet<>(); + String deviceManagerRoleHolderPackageName = + mInjector.getDeviceManagerRoleHolderPackageName(mContext); + if (deviceManagerRoleHolderPackageName != null) { + result.add(deviceManagerRoleHolderPackageName); + } + return result; + } + /** * Returns a subset of {@code packageNames} whose packages are mainline modules declared as * required apps via their app metadata. diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 3584728a2e62..fe8f2235ed63 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -637,13 +637,15 @@ class Owners { } } - void setDeviceOwnerType(String packageName, @DeviceOwnerType int deviceOwnerType) { + void setDeviceOwnerType(String packageName, @DeviceOwnerType int deviceOwnerType, + boolean isAdminTestOnly) { synchronized (mLock) { if (!hasDeviceOwner()) { Slog.e(TAG, "Attempting to set a device owner type when there is no device owner"); return; - } else if (isDeviceOwnerTypeSetForDeviceOwner(packageName)) { - Slog.e(TAG, "Device owner type for " + packageName + " has already been set"); + } else if (!isAdminTestOnly && isDeviceOwnerTypeSetForDeviceOwner(packageName)) { + Slog.e(TAG, "Setting the device owner type more than once is only allowed" + + " for test only admins"); return; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 74e04ed6e58b..c9aeabd17191 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -176,6 +176,7 @@ import com.android.server.power.hint.HintManagerService; import com.android.server.powerstats.PowerStatsService; import com.android.server.profcollect.ProfcollectForwardingService; import com.android.server.recoverysystem.RecoverySystemService; +import com.android.server.resources.ResourcesManagerService; import com.android.server.restrictions.RestrictionsManagerService; import com.android.server.role.RoleServicePlatformHelper; import com.android.server.rotationresolver.RotationResolverManagerService; @@ -265,8 +266,6 @@ public final class SystemServer implements Dumpable { "/apex/com.android.os.statsd/javalib/service-statsd.jar"; private static final String CONNECTIVITY_SERVICE_APEX_PATH = "/apex/com.android.tethering/javalib/service-connectivity.jar"; - private static final String NEARBY_SERVICE_APEX_PATH = - "/apex/com.android.nearby/javalib/service-nearby.jar"; private static final String STATS_COMPANION_LIFECYCLE_CLASS = "com.android.server.stats.StatsCompanion$Lifecycle"; private static final String STATS_PULL_ATOM_SERVICE_CLASS = @@ -277,8 +276,6 @@ public final class SystemServer implements Dumpable { "com.android.server.usb.UsbService$Lifecycle"; private static final String MIDI_SERVICE_CLASS = "com.android.server.midi.MidiService$Lifecycle"; - private static final String NEARBY_SERVICE_CLASS = - "com.android.server.nearby.NearbyService"; private static final String WIFI_APEX_SERVICE_JAR_PATH = "/apex/com.android.wifi/javalib/service-wifi.jar"; private static final String WIFI_SERVICE_CLASS = @@ -1289,6 +1286,13 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(new OverlayManagerService(mSystemContext)); t.traceEnd(); + // Manages Resources packages + t.traceBegin("StartResourcesManagerService"); + ResourcesManagerService resourcesService = new ResourcesManagerService(mSystemContext); + resourcesService.setActivityManagerService(mActivityManagerService); + mSystemServiceManager.startService(resourcesService); + t.traceEnd(); + t.traceBegin("StartSensorPrivacyService"); mSystemServiceManager.startService(new SensorPrivacyService(mSystemContext)); t.traceEnd(); @@ -2000,16 +2004,6 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); - // Start Nearby Service. - t.traceBegin("StartNearbyService"); - try { - mSystemServiceManager.startServiceFromJar(NEARBY_SERVICE_CLASS, - NEARBY_SERVICE_APEX_PATH); - } catch (Throwable e) { - reportWtf("starting NearbyService", e); - } - t.traceEnd(); - t.traceBegin("StartConnectivityService"); // This has to be called after NetworkManagementService, NetworkStatsService // and NetworkPolicyManager because ConnectivityService needs to take these diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java index fec9b1249d17..e89c812ba1fb 100644 --- a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java +++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java @@ -359,6 +359,34 @@ public final class GameSessionTest { LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); } + @Test + public void dispatchTransientVisibilityChanged_valueUnchanged_doesNotInvokeCallback() { + mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false); + + assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures).hasSize(0); + } + + @Test + public void dispatchTransientVisibilityChanged_valueChanged_invokesCallback() { + mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true); + + assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures) + .containsExactly(true).inOrder(); + } + + @Test + public void dispatchTransientVisibilityChanged_manyTimes_invokesCallbackWhenValueChanges() { + mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false); + mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true); + mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false); + mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(false); + mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true); + mGameSession.dispatchTransientSystemBarVisibilityFromRevealGestureChanged(true); + + assertThat(mGameSession.mCapturedTransientSystemBarVisibilityFromRevealGestures) + .containsExactly(true, false, true).inOrder(); + } + private static class LifecycleTrackingGameSession extends GameSession { private enum LifecycleMethodCall { ON_CREATE, @@ -368,6 +396,8 @@ public final class GameSessionTest { } final List<LifecycleMethodCall> mLifecycleMethodCalls = new ArrayList<>(); + final List<Boolean> mCapturedTransientSystemBarVisibilityFromRevealGestures = + new ArrayList<>(); @Override public void onCreate() { @@ -387,5 +417,11 @@ public final class GameSessionTest { mLifecycleMethodCalls.add(LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED); } } + + @Override + public void onTransientSystemBarVisibilityFromRevealGestureChanged( + boolean visibleDueToGesture) { + mCapturedTransientSystemBarVisibilityFromRevealGestures.add(visibleDueToGesture); + } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index eed2d4251cd7..44b81d4d4100 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -23,20 +23,27 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.Manifest; import android.app.GameManager; import android.app.GameModeInfo; +import android.app.GameState; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.hardware.power.Mode; import android.os.Bundle; +import android.os.PowerManagerInternal; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; @@ -46,6 +53,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.LocalServices; import com.android.server.SystemService; import org.junit.After; @@ -76,6 +84,8 @@ public class GameManagerServiceTests { private TestLooper mTestLooper; @Mock private PackageManager mMockPackageManager; + @Mock + private PowerManagerInternal mMockPowerManager; // Stolen from ConnectivityServiceTest.MockContext class MockContext extends ContextWrapper { @@ -158,10 +168,12 @@ public class GameManagerServiceTests { .thenReturn(packages); when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) .thenReturn(applicationInfo); + LocalServices.addService(PowerManagerInternal.class, mMockPowerManager); } @After public void tearDown() throws Exception { + LocalServices.removeServiceForTest(PowerManagerInternal.class); GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.disableCompatScale(mPackageName); if (mMockingSession != null) { @@ -1052,4 +1064,41 @@ public class GameManagerServiceTests { assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameModeInfo.getActiveGameMode()); assertEquals(0, gameModeInfo.getAvailableGameModes().length); } + + @Test + public void testGameStateLoadingRequiresPerformanceMode() { + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + GameState gameState = new GameState(true, GameState.MODE_NONE); + gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); + mTestLooper.dispatchAll(); + verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean()); + } + + private void setGameState(boolean isLoading) { + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameMode( + mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); + GameState gameState = new GameState(isLoading, GameState.MODE_NONE); + gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); + mTestLooper.dispatchAll(); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, isLoading); + } + + @Test + public void testSetGameStateLoading() { + setGameState(true); + } + + @Test + public void testSetGameStateNotLoading() { + setGameState(false); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java index d5e4710095f2..08de62bc6537 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java @@ -26,13 +26,14 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; +import android.Manifest; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; @@ -70,6 +71,7 @@ import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.FunctionalUtils.ThrowingConsumer; import com.android.internal.util.Preconditions; import com.android.server.wm.WindowManagerInternal; +import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener; import com.android.server.wm.WindowManagerService; import org.junit.After; @@ -83,6 +85,7 @@ import org.mockito.quality.Strictness; import java.util.ArrayList; import java.util.HashMap; +import java.util.Objects; /** @@ -109,6 +112,7 @@ public final class GameServiceProviderInstanceImplTest { private static final ComponentName GAME_B_MAIN_ACTIVITY = new ComponentName(GAME_B_PACKAGE, "com.package.game.b.MainActivity"); + private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); private MockitoSession mMockingSession; @@ -121,13 +125,14 @@ public final class GameServiceProviderInstanceImplTest { private WindowManagerInternal mMockWindowManagerInternal; @Mock private IActivityManager mMockActivityManager; - private FakeContext mFakeContext; + private MockContext mMockContext; private FakeGameClassifier mFakeGameClassifier; private FakeGameService mFakeGameService; private FakeServiceConnector<IGameService> mFakeGameServiceConnector; private FakeGameSessionService mFakeGameSessionService; private FakeServiceConnector<IGameSessionService> mFakeGameSessionServiceConnector; private ArrayList<ITaskStackListener> mTaskStackListeners; + private ArrayList<TaskSystemBarsListener> mTaskSystemBarsListeners; private ArrayList<RunningTaskInfo> mRunningTaskInfos; @Mock @@ -140,7 +145,7 @@ public final class GameServiceProviderInstanceImplTest { .strictness(Strictness.LENIENT) .startMocking(); - mFakeContext = new FakeContext(InstrumentationRegistry.getInstrumentation().getContext()); + mMockContext = new MockContext(InstrumentationRegistry.getInstrumentation().getContext()); mFakeGameClassifier = new FakeGameClassifier(); mFakeGameClassifier.recordGamePackage(GAME_A_PACKAGE); @@ -156,20 +161,30 @@ public final class GameServiceProviderInstanceImplTest { mTaskStackListeners.add(invocation.getArgument(0)); return null; }).when(mMockActivityTaskManager).registerTaskStackListener(any()); + doAnswer(invocation -> { + mTaskStackListeners.remove(invocation.getArgument(0)); + return null; + }).when(mMockActivityTaskManager).unregisterTaskStackListener(any()); + + mTaskSystemBarsListeners = new ArrayList<>(); + doAnswer(invocation -> { + mTaskSystemBarsListeners.add(invocation.getArgument(0)); + return null; + }).when(mMockWindowManagerInternal).registerTaskSystemBarsListener(any()); + doAnswer(invocation -> { + mTaskSystemBarsListeners.remove(invocation.getArgument(0)); + return null; + }).when(mMockWindowManagerInternal).unregisterTaskSystemBarsListener(any()); mRunningTaskInfos = new ArrayList<>(); when(mMockActivityTaskManager.getTasks(anyInt(), anyBoolean(), anyBoolean())).thenReturn( mRunningTaskInfos); - doAnswer(invocation -> { - mTaskStackListeners.remove(invocation.getArgument(0)); - return null; - }).when(mMockActivityTaskManager).unregisterTaskStackListener(any()); mGameServiceProviderInstance = new GameServiceProviderInstanceImpl( new UserHandle(USER_ID), ConcurrentUtils.DIRECT_EXECUTOR, - mFakeContext, + mMockContext, mFakeGameClassifier, mMockActivityManager, mMockActivityTaskManager, @@ -301,6 +316,7 @@ public final class GameServiceProviderInstanceImplTest { throws Exception { mGameServiceProviderInstance.start(); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty(); @@ -322,6 +338,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSessionService.CapturedCreateInvocation capturedCreateInvocation = @@ -336,6 +353,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); dispatchTaskCreated(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); assertThat(mFakeGameSessionService.getCapturedCreateInvocations()).isEmpty(); @@ -345,6 +363,7 @@ public final class GameServiceProviderInstanceImplTest { public void gameTaskStartedAndSessionRequested_createsGameSession() throws Exception { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -362,7 +381,9 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); CreateGameSessionRequest expectedCreateGameSessionRequest = new CreateGameSessionRequest(10, @@ -376,6 +397,7 @@ public final class GameServiceProviderInstanceImplTest { public void gameSessionSuccessfullyCreated_createsTaskOverlay() throws Exception { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -384,13 +406,67 @@ public final class GameServiceProviderInstanceImplTest { .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10)); - verifyNoMoreInteractions(mMockWindowManagerInternal); + } + + @Test + public void taskSystemBarsListenerChanged_noAssociatedGameSession_doesNothing() { + mGameServiceProviderInstance.start(); + + dispatchTaskSystemBarsEvent(taskSystemBarsListener -> { + taskSystemBarsListener.onTransientSystemBarsVisibilityChanged( + 10, + /* areVisible= */ false, + /* wereRevealedFromSwipeOnSystemBar= */ false); + }); + } + + @Test + public void systemBarsTransientShownDueToGesture_hasGameSession_propagatesToGameSession() { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + dispatchTaskSystemBarsEvent(taskSystemBarsListener -> { + taskSystemBarsListener.onTransientSystemBarsVisibilityChanged( + 10, + /* areVisible= */ true, + /* wereRevealedFromSwipeOnSystemBar= */ true); + }); + + assertThat(gameSession10.mAreTransientSystemBarsVisibleFromRevealGesture).isTrue(); + } + + @Test + public void systemBarsTransientShownButNotGesture_hasGameSession_notPropagatedToGameSession() { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + dispatchTaskSystemBarsEvent(taskSystemBarsListener -> { + taskSystemBarsListener.onTransientSystemBarsVisibilityChanged( + 10, + /* areVisible= */ true, + /* wereRevealedFromSwipeOnSystemBar= */ false); + }); + + assertThat(gameSession10.mAreTransientSystemBarsVisibleFromRevealGesture).isFalse(); } @Test public void gameTaskFocused_propagatedToGameSession() throws Exception { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -416,6 +492,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -432,6 +509,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); dispatchTaskRemoved(10); @@ -449,6 +527,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -466,6 +545,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -477,7 +557,6 @@ public final class GameServiceProviderInstanceImplTest { verify(mMockWindowManagerInternal).addTaskOverlay(eq(10), eq(mockSurfacePackage10)); verify(mMockWindowManagerInternal).removeTaskOverlay(eq(10), eq(mockSurfacePackage10)); - verifyNoMoreInteractions(mMockWindowManagerInternal); } @Test @@ -486,6 +565,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -513,6 +593,7 @@ public final class GameServiceProviderInstanceImplTest { startTask(10, GAME_A_MAIN_ACTIVITY); startTask(11, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -530,6 +611,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -557,6 +639,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -586,6 +669,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -619,10 +703,19 @@ public final class GameServiceProviderInstanceImplTest { } @Test + public void createGameSession_failurePermissionDenied() throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY); + assertThrows(SecurityException.class, () -> mFakeGameService.requestCreateGameSession(10)); + } + + @Test public void stop_severalActiveGameSessions_destroysGameSessionsAndUnbinds() throws Exception { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -650,6 +743,7 @@ public final class GameServiceProviderInstanceImplTest { public void takeScreenshot_failureNoBitmapCaptured() throws Exception { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); IGameSessionController gameSessionController = getOnlyElement( @@ -669,6 +763,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); IGameSessionController gameSessionController = getOnlyElement( @@ -683,6 +778,7 @@ public final class GameServiceProviderInstanceImplTest { @Test public void restartGame_taskIdAssociatedWithGame_restartsTargetGame() throws Exception { + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); Intent launchIntent = new Intent("com.test.ACTION_LAUNCH_GAME_PACKAGE") .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); when(mMockPackageManager.getLaunchIntentForPackage(GAME_A_PACKAGE)) @@ -691,6 +787,7 @@ public final class GameServiceProviderInstanceImplTest { mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -710,14 +807,16 @@ public final class GameServiceProviderInstanceImplTest { .mGameSessionController.restartGame(10); verify(mMockActivityManager).forceStopPackage(GAME_A_PACKAGE, UserHandle.USER_CURRENT); - assertThat(mFakeContext.getLastStartedIntent()).isEqualTo(launchIntent); + assertThat(mMockContext.getLastStartedIntent()).isEqualTo(launchIntent); } @Test public void restartGame_taskIdNotAssociatedWithGame_noOp() throws Exception { + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mGameServiceProviderInstance.start(); startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); mFakeGameService.requestCreateGameSession(10); FakeGameSession gameSession10 = new FakeGameSession(); @@ -730,7 +829,20 @@ public final class GameServiceProviderInstanceImplTest { .mGameSessionController.restartGame(11); verifyZeroInteractions(mMockActivityManager); - assertThat(mFakeContext.getLastStartedIntent()).isNull(); + assertThat(mMockContext.getLastStartedIntent()).isNull(); + } + + @Test + public void restartGame_failurePermissionDenied() throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mockPermissionGranted(Manifest.permission.MANAGE_GAME_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + IGameSessionController gameSessionController = Objects.requireNonNull(getOnlyElement( + mFakeGameSessionService.getCapturedCreateInvocations())).mGameSessionController; + mockPermissionDenied(Manifest.permission.MANAGE_GAME_ACTIVITY); + assertThrows(SecurityException.class, + () -> gameSessionController.restartGame(10)); } private void startTask(int taskId, ComponentName componentName) { @@ -774,6 +886,21 @@ public final class GameServiceProviderInstanceImplTest { } } + private void mockPermissionGranted(String permission) { + mMockContext.setPermission(permission, PackageManager.PERMISSION_GRANTED); + } + + private void mockPermissionDenied(String permission) { + mMockContext.setPermission(permission, PackageManager.PERMISSION_DENIED); + } + + private void dispatchTaskSystemBarsEvent( + ThrowingConsumer<TaskSystemBarsListener> taskSystemBarsListenerConsumer) { + for (TaskSystemBarsListener listener : mTaskSystemBarsListeners) { + taskSystemBarsListenerConsumer.accept(listener); + } + } + static final class FakeGameService extends IGameService.Stub { private IGameServiceController mGameServiceController; @@ -888,6 +1015,7 @@ public final class GameServiceProviderInstanceImplTest { private static class FakeGameSession extends IGameSession.Stub { boolean mIsDestroyed = false; boolean mIsFocused = false; + boolean mAreTransientSystemBarsVisibleFromRevealGesture = false; @Override public void onDestroyed() { @@ -898,15 +1026,35 @@ public final class GameServiceProviderInstanceImplTest { public void onTaskFocusChanged(boolean focused) { mIsFocused = focused; } + + @Override + public void onTransientSystemBarVisibilityFromRevealGestureChanged(boolean areVisible) { + mAreTransientSystemBarsVisibleFromRevealGesture = areVisible; + } } - private final class FakeContext extends ContextWrapper { + private final class MockContext extends ContextWrapper { private Intent mLastStartedIntent; + // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant + private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); - FakeContext(Context base) { + MockContext(Context base) { super(base); } + /** + * Mock checks for the specified permission, and have them behave as per {@code granted}. + * + * <p>Passing null reverts to default behavior, which does a real permission check on the + * test package. + * + * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or + * {@link PackageManager#PERMISSION_DENIED}. + */ + public void setPermission(String permission, Integer granted) { + mMockedPermissions.put(permission, granted); + } + @Override public PackageManager getPackageManager() { return mMockPackageManager; @@ -919,12 +1067,19 @@ public final class GameServiceProviderInstanceImplTest { @Override public void enforceCallingPermission(String permission, @Nullable String message) { - // Do nothing. + final Integer granted = mMockedPermissions.get(permission); + if (granted == null) { + super.enforceCallingOrSelfPermission(permission, message); + return; + } + + if (!granted.equals(PackageManager.PERMISSION_GRANTED)) { + throw new SecurityException("[Test] permission denied: " + permission); + } } Intent getLastStartedIntent() { return mLastStartedIntent; } } - } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java index bdfdf7723c02..64657a91224a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -50,7 +50,7 @@ import android.util.IntArray; import android.util.SparseArray; import com.android.dx.mockito.inline.extended.ExtendedMockito; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import com.android.internal.os.BackgroundThread; import com.android.internal.util.Preconditions; @@ -101,12 +101,12 @@ public class StagingManagerTest { mMockitoSession = ExtendedMockito.mockitoSession() .strictness(Strictness.LENIENT) .mockStatic(SystemProperties.class) - .mockStatic(PackageHelper.class) + .mockStatic(InstallLocationUtils.class) .startMocking(); when(mStorageManager.supportsCheckpoint()).thenReturn(true); when(mStorageManager.needsCheckpoint()).thenReturn(true); - when(PackageHelper.getStorageManager()).thenReturn(mStorageManager); + when(InstallLocationUtils.getStorageManager()).thenReturn(mStorageManager); when(SystemProperties.get(eq("ro.apex.updatable"))).thenReturn("true"); when(SystemProperties.get(eq("ro.apex.updatable"), anyString())).thenReturn("true"); diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java new file mode 100644 index 000000000000..c325778a5683 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Intent; +import android.os.Looper; +import android.testing.AndroidTestingRunner; +import android.testing.TestableContext; +import android.testing.TestableLooper; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.internal.R; +import com.android.internal.util.test.BroadcastInterceptingContext; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.ExecutionException; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class DockObserverTest { + + @Rule + public TestableContext mContext = + new TestableContext(ApplicationProvider.getApplicationContext(), null); + + private final BroadcastInterceptingContext mInterceptingContext = + new BroadcastInterceptingContext(mContext); + + BroadcastInterceptingContext.FutureIntent updateExtconDockState(DockObserver observer, + String extconDockState) { + BroadcastInterceptingContext.FutureIntent futureIntent = + mInterceptingContext.nextBroadcastIntent(Intent.ACTION_DOCK_EVENT); + observer.setDockStateFromProviderForTesting( + DockObserver.ExtconStateProvider.fromString(extconDockState)); + TestableLooper.get(this).processAllMessages(); + return futureIntent; + } + + DockObserver observerWithMappingConfig(String[] configEntries) { + mContext.getOrCreateTestableResources().addOverride( + R.array.config_dockExtconStateMapping, + configEntries); + return new DockObserver(mInterceptingContext); + } + + void assertDockEventIntentWithExtraThenUndock(DockObserver observer, String extconDockState, + int expectedExtra) throws ExecutionException, InterruptedException { + assertThat(updateExtconDockState(observer, extconDockState) + .get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1)) + .isEqualTo(expectedExtra); + assertThat(updateExtconDockState(observer, "DOCK=0") + .get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1)) + .isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED); + } + + @Before + public void setUp() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + } + + @Test + public void testDockIntentBroadcast_onlyAfterBootReady() + throws ExecutionException, InterruptedException { + DockObserver observer = new DockObserver(mInterceptingContext); + BroadcastInterceptingContext.FutureIntent futureIntent = + updateExtconDockState(observer, "DOCK=1"); + updateExtconDockState(observer, "DOCK=1").assertNotReceived(); + // Last boot phase reached + observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); + TestableLooper.get(this).processAllMessages(); + assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1)) + .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK); + } + + @Test + public void testDockIntentBroadcast_customConfigResource() + throws ExecutionException, InterruptedException { + DockObserver observer = observerWithMappingConfig( + new String[] {"2,KEY1=1,KEY2=2", "3,KEY3=3"}); + observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); + + // Mapping should not match + assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1", + Intent.EXTRA_DOCK_STATE_DESK); + assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY1=1", + Intent.EXTRA_DOCK_STATE_DESK); + assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY2=2", + Intent.EXTRA_DOCK_STATE_DESK); + + // 1st mapping now matches + assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY2=2\nKEY1=1", + Intent.EXTRA_DOCK_STATE_CAR); + + // 2nd mapping now matches + assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY3=3", + Intent.EXTRA_DOCK_STATE_LE_DESK); + } + + @Test + public void testDockIntentBroadcast_customConfigResourceWithWildcard() + throws ExecutionException, InterruptedException { + DockObserver observer = observerWithMappingConfig(new String[] { + "2,KEY2=2", + "3,KEY3=3", + "4" + }); + observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); + assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5", + Intent.EXTRA_DOCK_STATE_HE_DESK); + } +} diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java index 9ee1205b4325..3890d4d006e2 100644 --- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java @@ -24,14 +24,20 @@ import static com.android.server.attention.AttentionManagerService.KEY_STALE_AFT import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.fail; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import android.attention.AttentionManagerInternal.AttentionCallbackInternal; +import android.attention.AttentionManagerInternal.ProximityCallbackInternal; import android.content.ComponentName; import android.content.Context; import android.os.IBinder; @@ -42,6 +48,7 @@ import android.os.RemoteException; import android.provider.DeviceConfig; import android.service.attention.IAttentionCallback; import android.service.attention.IAttentionService; +import android.service.attention.IProximityCallback; import androidx.test.filters.SmallTest; @@ -49,6 +56,7 @@ import com.android.server.attention.AttentionManagerService.AttentionCheck; import com.android.server.attention.AttentionManagerService.AttentionCheckCache; import com.android.server.attention.AttentionManagerService.AttentionCheckCacheBuffer; import com.android.server.attention.AttentionManagerService.AttentionHandler; +import com.android.server.attention.AttentionManagerService.ProximityUpdate; import org.junit.Before; import org.junit.Test; @@ -59,10 +67,13 @@ import org.mockito.MockitoAnnotations; /** * Tests for {@link com.android.server.attention.AttentionManagerService} */ +@SuppressWarnings("GuardedBy") @SmallTest public class AttentionManagerServiceTest { + private static final double PROXIMITY_SUCCESS_STATE = 1.0; private AttentionManagerService mSpyAttentionManager; private final int mTimeout = 1000; + private final Object mLock = new Object(); @Mock private AttentionCallbackInternal mMockAttentionCallbackInternal; @Mock @@ -73,6 +84,8 @@ public class AttentionManagerServiceTest { private IThermalService mMockIThermalService; @Mock Context mContext; + @Mock + private ProximityCallbackInternal mMockProximityCallbackInternal; @Before public void setUp() throws RemoteException { @@ -84,7 +97,6 @@ public class AttentionManagerServiceTest { doReturn(true).when(mMockIPowerManager).isInteractive(); mPowerManager = new PowerManager(mContext, mMockIPowerManager, mMockIThermalService, null); - Object mLock = new Object(); // setup a spy on attention manager AttentionManagerService attentionManager = new AttentionManagerService( mContext, @@ -100,6 +112,119 @@ public class AttentionManagerServiceTest { mSpyAttentionManager); mSpyAttentionManager.mCurrentAttentionCheck = attentionCheck; mSpyAttentionManager.mService = new MockIAttentionService(); + doNothing().when(mSpyAttentionManager).freeIfInactiveLocked(); + } + + @Test + public void testRegisterProximityUpdates_returnFalseWhenServiceDisabled() { + mSpyAttentionManager.mIsServiceEnabled = false; + + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isFalse(); + } + + @Test + public void testRegisterProximityUpdates_returnFalseWhenServiceUnavailable() { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(false).when(mSpyAttentionManager).isServiceAvailable(); + + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isFalse(); + } + + @Test + public void testRegisterProximityUpdates_returnFalseWhenPowerManagerNotInteract() + throws RemoteException { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(false).when(mMockIPowerManager).isInteractive(); + + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isFalse(); + } + + @Test + public void testRegisterProximityUpdates_callOnSuccess() throws RemoteException { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(true).when(mMockIPowerManager).isInteractive(); + + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isTrue(); + verify(mMockProximityCallbackInternal, times(1)) + .onProximityUpdate(PROXIMITY_SUCCESS_STATE); + } + + @Test + public void testRegisterProximityUpdates_callOnSuccessTwiceInARow() throws RemoteException { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(true).when(mMockIPowerManager).isInteractive(); + + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isTrue(); + + ProximityUpdate prevProximityUpdate = mSpyAttentionManager.mCurrentProximityUpdate; + assertThat(mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal)) + .isTrue(); + assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isEqualTo(prevProximityUpdate); + verify(mMockProximityCallbackInternal, times(1)) + .onProximityUpdate(PROXIMITY_SUCCESS_STATE); + } + + @Test + public void testUnregisterProximityUpdates_noCrashWhenNoCallbackIsRegistered() { + mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); + verifyZeroInteractions(mMockProximityCallbackInternal); + } + + @Test + public void testUnregisterProximityUpdates_noCrashWhenCallbackMismatched() + throws RemoteException { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(true).when(mMockIPowerManager).isInteractive(); + mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal); + verify(mMockProximityCallbackInternal, times(1)) + .onProximityUpdate(PROXIMITY_SUCCESS_STATE); + + ProximityCallbackInternal mismatchedCallback = new ProximityCallbackInternal() { + @Override + public void onProximityUpdate(double distance) { + fail("Callback shouldn't have responded."); + } + }; + mSpyAttentionManager.onStopProximityUpdates(mismatchedCallback); + + verifyNoMoreInteractions(mMockProximityCallbackInternal); + } + + @Test + public void testUnregisterProximityUpdates_cancelRegistrationWhenMatched() + throws RemoteException { + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(true).when(mMockIPowerManager).isInteractive(); + mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal); + mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); + + assertThat(mSpyAttentionManager.mCurrentProximityUpdate).isNull(); + } + + @Test + public void testUnregisterProximityUpdates_noCrashWhenTwiceInARow() throws RemoteException { + // Attention Service registers proximity updates. + mSpyAttentionManager.mIsServiceEnabled = true; + doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); + doReturn(true).when(mMockIPowerManager).isInteractive(); + mSpyAttentionManager.onStartProximityUpdates(mMockProximityCallbackInternal); + verify(mMockProximityCallbackInternal, times(1)) + .onProximityUpdate(PROXIMITY_SUCCESS_STATE); + + // Attention Service unregisters the proximity update twice in a row. + mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); + mSpyAttentionManager.onStopProximityUpdates(mMockProximityCallbackInternal); + verifyNoMoreInteractions(mMockProximityCallbackInternal); } @Test @@ -127,7 +252,6 @@ public class AttentionManagerServiceTest { mSpyAttentionManager.mIsServiceEnabled = true; doReturn(true).when(mSpyAttentionManager).isServiceAvailable(); doReturn(true).when(mMockIPowerManager).isInteractive(); - doNothing().when(mSpyAttentionManager).freeIfInactiveLocked(); mSpyAttentionManager.mCurrentAttentionCheck = null; AttentionCallbackInternal callback = Mockito.mock(AttentionCallbackInternal.class); @@ -213,6 +337,13 @@ public class AttentionManagerServiceTest { public void cancelAttentionCheck(IAttentionCallback callback) { } + public void onStartProximityUpdates(IProximityCallback callback) throws RemoteException { + callback.onProximityUpdate(PROXIMITY_SUCCESS_STATE); + } + + public void onStopProximityUpdates() throws RemoteException { + } + public IBinder asBinder() { return null; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java new file mode 100644 index 000000000000..5746f6f2d446 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.log; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.StatusBarManager; +import android.hardware.biometrics.IBiometricContextListener; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.common.OperationReason; +import android.hardware.display.AmbientDisplayConfiguration; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.InstanceId; +import com.android.internal.statusbar.ISessionListener; +import com.android.internal.statusbar.IStatusBarService; + +import com.google.common.collect.ImmutableList; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +@Presubmit +@SmallTest +public class BiometricContextProviderTest { + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private IStatusBarService mStatusBarService; + @Mock + private ISessionListener mSessionListener; + @Mock + private AmbientDisplayConfiguration mAmbientDisplayConfiguration; + + private OperationContext mOpContext = new OperationContext(); + private IBiometricContextListener mListener; + private BiometricContextProvider mProvider; + + @Before + public void setup() throws RemoteException { + when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); + mProvider = new BiometricContextProvider(mAmbientDisplayConfiguration, mStatusBarService, + null /* handler */); + ArgumentCaptor<IBiometricContextListener> captor = + ArgumentCaptor.forClass(IBiometricContextListener.class); + verify(mStatusBarService).setBiometicContextListener(captor.capture()); + mListener = captor.getValue(); + ArgumentCaptor<ISessionListener> sessionCaptor = + ArgumentCaptor.forClass(ISessionListener.class); + verify(mStatusBarService).registerSessionListener(anyInt(), sessionCaptor.capture()); + mSessionListener = sessionCaptor.getValue(); + } + + @Test + public void testIsAoD() throws RemoteException { + mListener.onDozeChanged(true); + assertThat(mProvider.isAoD()).isTrue(); + mListener.onDozeChanged(false); + assertThat(mProvider.isAoD()).isFalse(); + + when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(false); + mListener.onDozeChanged(true); + assertThat(mProvider.isAoD()).isFalse(); + mListener.onDozeChanged(false); + assertThat(mProvider.isAoD()).isFalse(); + } + + @Test + public void testSubscribesToAoD() throws RemoteException { + final List<Boolean> expected = ImmutableList.of(true, false, true, true, false); + final List<Boolean> actual = new ArrayList<>(); + + mProvider.subscribe(mOpContext, ctx -> { + assertThat(ctx).isSameInstanceAs(mOpContext); + actual.add(ctx.isAoD); + }); + + for (boolean v : expected) { + mListener.onDozeChanged(v); + } + + assertThat(actual).containsExactlyElementsIn(expected).inOrder(); + } + + @Test + public void testUnsubscribes() throws RemoteException { + final Consumer<OperationContext> emptyConsumer = mock(Consumer.class); + mProvider.subscribe(mOpContext, emptyConsumer); + mProvider.unsubscribe(mOpContext); + + mListener.onDozeChanged(true); + + final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class); + mProvider.subscribe(mOpContext, nonEmptyConsumer); + mListener.onDozeChanged(false); + mProvider.unsubscribe(mOpContext); + mListener.onDozeChanged(true); + + verify(emptyConsumer, never()).accept(any()); + verify(nonEmptyConsumer).accept(same(mOpContext)); + } + + @Test + public void testSessionId() throws RemoteException { + final int keyguardSessionId = 10; + final int bpSessionId = 20; + + assertThat(mProvider.getBiometricPromptSessionId()).isNull(); + assertThat(mProvider.getKeyguardEntrySessionId()).isNull(); + + mSessionListener.onSessionStarted(StatusBarManager.SESSION_KEYGUARD, + InstanceId.fakeInstanceId(keyguardSessionId)); + + assertThat(mProvider.getBiometricPromptSessionId()).isNull(); + assertThat(mProvider.getKeyguardEntrySessionId()).isEqualTo(keyguardSessionId); + + mSessionListener.onSessionStarted(StatusBarManager.SESSION_BIOMETRIC_PROMPT, + InstanceId.fakeInstanceId(bpSessionId)); + + assertThat(mProvider.getBiometricPromptSessionId()).isEqualTo(bpSessionId); + assertThat(mProvider.getKeyguardEntrySessionId()).isEqualTo(keyguardSessionId); + + mSessionListener.onSessionEnded(StatusBarManager.SESSION_KEYGUARD, + InstanceId.fakeInstanceId(keyguardSessionId)); + + assertThat(mProvider.getBiometricPromptSessionId()).isEqualTo(bpSessionId); + assertThat(mProvider.getKeyguardEntrySessionId()).isNull(); + + mSessionListener.onSessionEnded(StatusBarManager.SESSION_BIOMETRIC_PROMPT, + InstanceId.fakeInstanceId(bpSessionId)); + + assertThat(mProvider.getBiometricPromptSessionId()).isNull(); + assertThat(mProvider.getKeyguardEntrySessionId()).isNull(); + } + + @Test + public void testUpdate() throws RemoteException { + mListener.onDozeChanged(false); + OperationContext context = mProvider.updateContext(mOpContext, false /* crypto */); + + // default state when nothing has been set + assertThat(context).isSameInstanceAs(mOpContext); + assertThat(mOpContext.id).isEqualTo(0); + assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN); + assertThat(mOpContext.isAoD).isEqualTo(false); + assertThat(mOpContext.isCrypto).isEqualTo(false); + + for (int type : List.of(StatusBarManager.SESSION_BIOMETRIC_PROMPT, + StatusBarManager.SESSION_KEYGUARD)) { + final int id = 40 + type; + final boolean aod = (type & 1) == 0; + + mListener.onDozeChanged(aod); + mSessionListener.onSessionStarted(type, InstanceId.fakeInstanceId(id)); + context = mProvider.updateContext(mOpContext, false /* crypto */); + assertThat(context).isSameInstanceAs(mOpContext); + assertThat(mOpContext.id).isEqualTo(id); + assertThat(mOpContext.reason).isEqualTo(reason(type)); + assertThat(mOpContext.isAoD).isEqualTo(aod); + assertThat(mOpContext.isCrypto).isEqualTo(false); + + mSessionListener.onSessionEnded(type, InstanceId.fakeInstanceId(id)); + } + + context = mProvider.updateContext(mOpContext, false /* crypto */); + assertThat(context).isSameInstanceAs(mOpContext); + assertThat(mOpContext.id).isEqualTo(0); + assertThat(mOpContext.reason).isEqualTo(OperationReason.UNKNOWN); + assertThat(mOpContext.isAoD).isEqualTo(false); + assertThat(mOpContext.isCrypto).isEqualTo(false); + } + + private static byte reason(int type) { + if (type == StatusBarManager.SESSION_BIOMETRIC_PROMPT) { + return OperationReason.BIOMETRIC_PROMPT; + } + if (type == StatusBarManager.SESSION_KEYGUARD) { + return OperationReason.KEYGUARD; + } + return OperationReason.UNKNOWN; + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java index 2b72fabe7cca..fe023374ba88 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java @@ -31,6 +31,7 @@ import android.hardware.Sensor; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.common.OperationContext; import android.hardware.input.InputSensorInfo; import android.platform.test.annotations.Presubmit; import android.testing.TestableContext; @@ -44,7 +45,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; @Presubmit @SmallTest @@ -55,6 +57,9 @@ public class BiometricLoggerTest { private static final int DEFAULT_CLIENT = BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT; @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Rule public TestableContext mContext = new TestableContext( InstrumentationRegistry.getInstrumentation().getContext()); @Mock @@ -64,12 +69,12 @@ public class BiometricLoggerTest { @Mock private BaseClientMonitor mClient; + private OperationContext mOpContext; private BiometricLogger mLogger; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - + mOpContext = new OperationContext(); mContext.addMockSystemService(SensorManager.class, mSensorManager); when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn( new Sensor(new InputSensorInfo("", "", 0, 0, Sensor.TYPE_LIGHT, 0, 0, 0, 0, 0, 0, @@ -91,14 +96,13 @@ public class BiometricLoggerTest { final int acquiredInfo = 2; final int vendorCode = 3; - final boolean isCrypto = true; final int targetUserId = 9; - mLogger.logOnAcquired(mContext, acquiredInfo, vendorCode, isCrypto, targetUserId); + mLogger.logOnAcquired(mContext, mOpContext, acquiredInfo, vendorCode, targetUserId); - verify(mSink).acquired( + verify(mSink).acquired(eq(mOpContext), eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), - eq(acquiredInfo), eq(vendorCode), eq(isCrypto), eq(targetUserId)); + eq(acquiredInfo), eq(vendorCode), eq(targetUserId)); } @Test @@ -107,17 +111,16 @@ public class BiometricLoggerTest { final boolean authenticated = true; final boolean requireConfirmation = false; - final boolean isCrypto = false; final int targetUserId = 11; final boolean isBiometricPrompt = true; - mLogger.logOnAuthenticated(mContext, - authenticated, requireConfirmation, isCrypto, targetUserId, isBiometricPrompt); + mLogger.logOnAuthenticated(mContext, mOpContext, + authenticated, requireConfirmation, targetUserId, isBiometricPrompt); - verify(mSink).authenticate( + verify(mSink).authenticate(eq(mOpContext), eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), - anyLong(), eq(authenticated), anyInt(), eq(requireConfirmation), eq(isCrypto), - eq(targetUserId), eq(isBiometricPrompt), anyFloat()); + anyLong(), anyInt(), eq(requireConfirmation), + eq(targetUserId), anyFloat()); } @Test @@ -141,14 +144,13 @@ public class BiometricLoggerTest { final int error = 7; final int vendorCode = 11; - final boolean isCrypto = false; final int targetUserId = 9; - mLogger.logOnError(mContext, error, vendorCode, isCrypto, targetUserId); + mLogger.logOnError(mContext, mOpContext, error, vendorCode, targetUserId); - verify(mSink).error( + verify(mSink).error(eq(mOpContext), eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT), anyBoolean(), - anyLong(), eq(error), eq(vendorCode), eq(isCrypto), eq(targetUserId)); + anyLong(), eq(error), eq(vendorCode), eq(targetUserId)); } @Test @@ -173,38 +175,34 @@ public class BiometricLoggerTest { private void testDisabledMetrics(boolean isBadConfig) { mLogger.disableMetrics(); - mLogger.logOnAcquired(mContext, + mLogger.logOnAcquired(mContext, mOpContext, 0 /* acquiredInfo */, 1 /* vendorCode */, - true /* isCrypto */, 8 /* targetUserId */); - mLogger.logOnAuthenticated(mContext, + mLogger.logOnAuthenticated(mContext, mOpContext, true /* authenticated */, true /* requireConfirmation */, - false /* isCrypto */, 4 /* targetUserId */, true/* isBiometricPrompt */); mLogger.logOnEnrolled(2 /* targetUserId */, 10 /* latency */, true /* enrollSuccessful */); - mLogger.logOnError(mContext, + mLogger.logOnError(mContext, mOpContext, 4 /* error */, 0 /* vendorCode */, - false /* isCrypto */, 6 /* targetUserId */); - verify(mSink, never()).acquired( + verify(mSink, never()).acquired(eq(mOpContext), anyInt(), anyInt(), anyInt(), anyBoolean(), - anyInt(), anyInt(), anyBoolean(), anyInt()); - verify(mSink, never()).authenticate( + anyInt(), anyInt(), anyInt()); + verify(mSink, never()).authenticate(eq(mOpContext), anyInt(), anyInt(), anyInt(), anyBoolean(), - anyLong(), anyBoolean(), anyInt(), anyBoolean(), - anyBoolean(), anyInt(), anyBoolean(), anyFloat()); + anyLong(), anyInt(), anyBoolean(), anyInt(), anyFloat()); verify(mSink, never()).enroll( anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat()); - verify(mSink, never()).error( + verify(mSink, never()).error(eq(mOpContext), anyInt(), anyInt(), anyInt(), anyBoolean(), - anyLong(), anyInt(), anyInt(), anyBoolean(), anyInt()); + anyLong(), anyInt(), anyInt(), anyInt()); mLogger.logUnknownEnrollmentInFramework(); mLogger.logUnknownEnrollmentInHal(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java index 9bb722f8a853..2d9d868f2f74 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/AcquisitionClientTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -34,6 +35,9 @@ import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; + import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -92,9 +96,8 @@ public class AcquisitionClientTest { @NonNull Supplier<Object> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter callback) { super(context, lazyDaemon, token, callback, 0 /* userId */, "Test", 0 /* cookie */, - TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */, 0 /* statsModality */, - 0 /* statsAction */, - 0 /* statsClient */); + TEST_SENSOR_ID /* sensorId */, true /* shouldVibrate */, + mock(BiometricLogger.class), mock(BiometricContext.class)); } @Override diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java index 51d234d5afeb..8e6d90c839d8 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java @@ -30,6 +30,7 @@ import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import org.junit.Before; @@ -49,6 +50,8 @@ public class BaseClientMonitorTest { @Mock private BiometricLogger mLogger; @Mock + private BiometricContext mBiometricContext; + @Mock private ClientMonitorCallback mCallback; private TestClientMonitor mClientMonitor; @@ -109,7 +112,7 @@ public class BaseClientMonitorTest { TestClientMonitor() { super(mContext, mToken, mListener, 9 /* userId */, "foo" /* owner */, 2 /* cookie */, - 5 /* sensorId */, mLogger); + 5 /* sensorId */, mLogger, mBiometricContext); } @Override diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java index 8751cf3810bf..64be56906d4b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java @@ -36,6 +36,9 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -54,7 +57,8 @@ public class BiometricSchedulerOperationTest { public abstract static class InterruptableMonitor<T> extends HalClientMonitor<T> implements Interruptable { public InterruptableMonitor() { - super(null, null, null, null, 0, null, 0, 0, 0, 0, 0); + super(null, null, null, null, 0, null, 0, 0, + mock(BiometricLogger.class), mock(BiometricContext.class)); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index ecd9abcb80e6..0fa2b41e8b32 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -51,6 +51,8 @@ import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.nano.BiometricSchedulerProto; import com.android.server.biometrics.nano.BiometricsProto; @@ -526,10 +528,10 @@ public class BiometricSchedulerTest { @NonNull ClientMonitorCallbackConverter listener) { super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */, false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */, - TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */, - 0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class), - false /* isKeyguard */, true /* shouldVibrate */, - false /* isKeyguardBypassEnabled */); + TEST_SENSOR_ID, mock(BiometricLogger.class), mock(BiometricContext.class), + true /* isStrongBiometric */, null /* taskStackListener */, + mock(LockoutTracker.class), false /* isKeyguard */, + true /* shouldVibrate */, false /* isKeyguardBypassEnabled */); } @Override @@ -573,8 +575,9 @@ public class BiometricSchedulerTest { @NonNull ClientMonitorCallbackConverter listener) { super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69], "test" /* owner */, mock(BiometricUtils.class), - 5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID, - true /* shouldVibrate */); + 5 /* timeoutSec */, TEST_SENSOR_ID, + true /* shouldVibrate */, + mock(BiometricLogger.class), mock(BiometricContext.class)); } @Override @@ -613,8 +616,8 @@ public class BiometricSchedulerTest { TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token, @NonNull Supplier<Object> lazyDaemon, int cookie, int protoEnum) { super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */, - TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */, - 0 /* statsAction */, 0 /* statsClient */); + TAG, cookie, TEST_SENSOR_ID, + mock(BiometricLogger.class), mock(BiometricContext.class)); mProtoEnum = protoEnum; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java index 587bb600f409..092ca191acba 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java @@ -64,7 +64,7 @@ public class CompositeCallbackTest { ClientMonitorCompositeCallback callback = new ClientMonitorCompositeCallback(callbacks); callback.onClientStarted(mClientMonitor); - final InOrder order = inOrder(expected); + final InOrder order = inOrder((Object[]) expected); for (ClientMonitorCallback cb : expected) { order.verify(cb).onClientStarted(eq(mClientMonitor)); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java index 30777cd79a0c..52eee9a55cc7 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java @@ -41,6 +41,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,6 +69,10 @@ public class UserAwareBiometricSchedulerTest { private Context mContext; @Mock private IBiometricService mBiometricService; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; private TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback(); private TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback(); @@ -88,7 +95,8 @@ public class UserAwareBiometricSchedulerTest { @Override public StopUserClient<?> getStopUserClient(int userId) { return new TestStopUserClient(mContext, Object::new, mToken, userId, - TEST_SENSOR_ID, mUserStoppedCallback); + TEST_SENSOR_ID, mBiometricLogger, mBiometricContext, + mUserStoppedCallback); } @NonNull @@ -96,7 +104,8 @@ public class UserAwareBiometricSchedulerTest { public StartUserClient<?, ?> getStartUserClient(int newUserId) { mStartUserClientCount++; return new TestStartUserClient(mContext, Object::new, mToken, newUserId, - TEST_SENSOR_ID, mUserStartedCallback, mStartOperationsFinish); + TEST_SENSOR_ID, mBiometricLogger, mBiometricContext, + mUserStartedCallback, mStartOperationsFinish); } }, CoexCoordinator.getInstance()); @@ -226,8 +235,10 @@ public class UserAwareBiometricSchedulerTest { private static class TestStopUserClient extends StopUserClient<Object> { public TestStopUserClient(@NonNull Context context, @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId, - int sensorId, @NonNull UserStoppedCallback callback) { - super(context, lazyDaemon, token, userId, sensorId, callback); + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, + @NonNull UserStoppedCallback callback) { + super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback); } @Override @@ -254,8 +265,10 @@ public class UserAwareBiometricSchedulerTest { public TestStartUserClient(@NonNull Context context, @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId, - int sensorId, @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) { - super(context, lazyDaemon, token, userId, sensorId, callback); + int sensorId, @NonNull BiometricLogger logger, + @NonNull BiometricContext biometricContext, + @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) { + super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback); mShouldFinish = shouldFinish; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java new file mode 100644 index 000000000000..aba93b058669 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.testing.TestableContext; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutCache; +import com.android.server.biometrics.sensors.face.UsageStats; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceAuthenticationClientTest { + + private static final int USER_ID = 12; + private static final long OP_ID = 32; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + + @Mock + private ISession mHal; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mClientMonitorCallbackConverter; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private LockoutCache mLockoutCache; + @Mock + private UsageStats mUsageStats; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Sensor.HalSessionCallback mHalSessionCallback; + @Captor + private ArgumentCaptor<OperationContext> mOperationContextCaptor; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Before + public void setup() { + when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer( + i -> i.getArgument(0)); + } + + @Test + public void authNoContext_v1() throws RemoteException { + final FaceAuthenticationClient client = createClient(1); + client.start(mCallback); + + verify(mHal).authenticate(eq(OP_ID)); + verify(mHal, never()).authenticateWithContext(anyLong(), any()); + } + + @Test + public void authWithContext_v2() throws RemoteException { + final FaceAuthenticationClient client = createClient(2); + client.start(mCallback); + + InOrder order = inOrder(mHal, mBiometricContext); + order.verify(mBiometricContext).updateContext( + mOperationContextCaptor.capture(), anyBoolean()); + order.verify(mHal).authenticateWithContext( + eq(OP_ID), same(mOperationContextCaptor.getValue())); + verify(mHal, never()).authenticate(anyLong()); + } + + private FaceAuthenticationClient createClient(int version) throws RemoteException { + when(mHal.getInterfaceVersion()).thenReturn(version); + + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + return new FaceAuthenticationClient(mContext, () -> aidl, mToken, + 2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID, + false /* restricted */, "test-owner", 4 /* cookie */, + false /* requireConfirmation */, 9 /* sensorId */, + mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, + mUsageStats, mLockoutCache, false /* allowBackgroundAuthentication */, + false /* isKeyguardBypassEnabled */, null /* sensorPrivacyManager */); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java new file mode 100644 index 000000000000..25135c6670db --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.testing.TestableContext; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceDetectClientTest { + + private static final int USER_ID = 12; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + + @Mock + private ISession mHal; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mClientMonitorCallbackConverter; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Sensor.HalSessionCallback mHalSessionCallback; + @Captor + private ArgumentCaptor<OperationContext> mOperationContextCaptor; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Before + public void setup() { + when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer( + i -> i.getArgument(0)); + } + + @Test + public void detectNoContext_v1() throws RemoteException { + final FaceDetectClient client = createClient(1); + client.start(mCallback); + + verify(mHal).detectInteraction(); + verify(mHal, never()).detectInteractionWithContext(any()); + } + + @Test + public void detectWithContext_v2() throws RemoteException { + final FaceDetectClient client = createClient(2); + client.start(mCallback); + + InOrder order = inOrder(mHal, mBiometricContext); + order.verify(mBiometricContext).updateContext( + mOperationContextCaptor.capture(), anyBoolean()); + order.verify(mHal).detectInteractionWithContext(same(mOperationContextCaptor.getValue())); + verify(mHal, never()).detectInteraction(); + } + + private FaceDetectClient createClient(int version) throws RemoteException { + when(mHal.getInterfaceVersion()).thenReturn(version); + + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + return new FaceDetectClient(mContext, () -> aidl, mToken, + 99 /* requestId */, mClientMonitorCallbackConverter, USER_ID, + "own-it", 5 /* sensorId */, mBiometricLogger, mBiometricContext, + false /* isStrongBiometric */, null /* sensorPrivacyManager */); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java new file mode 100644 index 000000000000..38e048bc1ba7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyByte; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.face.ISession; +import android.hardware.face.Face; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.testing.TestableContext; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceEnrollClientTest { + + private static final byte[] HAT = new byte[69]; + private static final int USER_ID = 12; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + + @Mock + private ISession mHal; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mClientMonitorCallbackConverter; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Face> mUtils; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Sensor.HalSessionCallback mHalSessionCallback; + @Captor + private ArgumentCaptor<OperationContext> mOperationContextCaptor; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Before + public void setup() { + when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer( + i -> i.getArgument(0)); + } + + @Test + public void enrollNoContext_v1() throws RemoteException { + final FaceEnrollClient client = createClient(1); + client.start(mCallback); + + verify(mHal).enroll(any(), anyByte(), any(), any()); + verify(mHal, never()).enrollWithContext(any(), anyByte(), any(), any(), any()); + } + + @Test + public void enrollWithContext_v2() throws RemoteException { + final FaceEnrollClient client = createClient(2); + client.start(mCallback); + + InOrder order = inOrder(mHal, mBiometricContext); + order.verify(mBiometricContext).updateContext( + mOperationContextCaptor.capture(), anyBoolean()); + order.verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), + same(mOperationContextCaptor.getValue())); + verify(mHal, never()).enroll(any(), anyByte(), any(), any()); + } + + private FaceEnrollClient createClient(int version) throws RemoteException { + when(mHal.getInterfaceVersion()).thenReturn(version); + + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter, + USER_ID, HAT, "com.foo.bar", 44 /* requestId */, + mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */, + null /* previewSurface */, 8 /* sensorId */, + mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */, + true /* debugConsent */); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java index 0ac00aafbf6c..12b8264fc20c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java @@ -37,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.HalClientMonitor; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -60,6 +61,8 @@ public class FaceProviderTest { private UserManager mUserManager; @Mock private IFace mDaemon; + @Mock + private BiometricContext mBiometricContext; private SensorProps[] mSensorProps; private LockoutResetDispatcher mLockoutResetDispatcher; @@ -89,7 +92,7 @@ public class FaceProviderTest { mLockoutResetDispatcher = new LockoutResetDispatcher(mContext); mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mSensorProps, TAG, - mLockoutResetDispatcher); + mLockoutResetDispatcher, mBiometricContext); } @SuppressWarnings("rawtypes") @@ -139,8 +142,9 @@ public class FaceProviderTest { @NonNull Context context, @NonNull SensorProps[] props, @NonNull String halInstanceName, - @NonNull LockoutResetDispatcher lockoutResetDispatcher) { - super(context, props, halInstanceName, lockoutResetDispatcher); + @NonNull LockoutResetDispatcher lockoutResetDispatcher, + @NonNull BiometricContext biometricContext) { + super(context, props, halInstanceName, lockoutResetDispatcher, biometricContext); mDaemon = daemon; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index 61e4776e9c50..b60324e88f15 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -32,6 +32,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.CoexCoordinator; import com.android.server.biometrics.sensors.LockoutCache; @@ -66,6 +68,10 @@ public class SensorTest { private Sensor.HalSessionCallback.Callback mHalSessionCallback; @Mock private LockoutResetDispatcher mLockoutResetDispatcher; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); @@ -102,7 +108,8 @@ public class SensorTest { mScheduler.scheduleClientMonitor(new FaceResetLockoutClient(mContext, () -> new AidlSession(1, mSession, USER_ID, mHalCallback), - USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache, mLockoutResetDispatcher)); + USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, + HAT, mLockoutCache, mLockoutResetDispatcher)); mLooper.dispatchAll(); verifyNotLocked(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java index 21a7a8ae65b9..116d2d5a66a0 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java @@ -41,6 +41,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -70,6 +71,8 @@ public class Face10Test { private UserManager mUserManager; @Mock private BiometricScheduler mScheduler; + @Mock + private BiometricContext mBiometricContext; private final Handler mHandler = new Handler(Looper.getMainLooper()); private LockoutResetDispatcher mLockoutResetDispatcher; @@ -100,7 +103,8 @@ public class Face10Test { resetLockoutRequiresChallenge); Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST")); - mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler); + mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, + mBiometricContext); mBinder = new Binder(); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java index 931fad14888e..ec083294c0bd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java @@ -34,6 +34,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.core.app.ApplicationProvider; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -62,6 +64,10 @@ public class FaceGenerateChallengeClientTest { private IFaceServiceReceiver mOtherReceiver; @Mock private ClientMonitorCallback mMonitorCallback; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; private FaceGenerateChallengeClient mClient; @@ -75,7 +81,7 @@ public class FaceGenerateChallengeClientTest { mClient = new FaceGenerateChallengeClient(mContext, () -> mIBiometricsFace, new Binder(), new ClientMonitorCallbackConverter(mClientReceiver), USER_ID, - TAG, SENSOR_ID, START_TIME); + TAG, SENSOR_ID, mBiometricLogger, mBiometricContext , START_TIME); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java new file mode 100644 index 000000000000..de0f038e8ec5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyFloat; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.PointerContext; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.ISidefpsController; +import android.hardware.fingerprint.IUdfpsOverlayController; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.testing.TestableContext; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.log.CallbackWithProbe; +import com.android.server.biometrics.log.Probe; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutCache; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.function.Consumer; + +@Presubmit +@SmallTest +public class FingerprintAuthenticationClientTest { + + private static final int USER_ID = 8; + private static final long OP_ID = 7; + private static final int POINTER_ID = 0; + private static final int TOUCH_X = 8; + private static final int TOUCH_Y = 20; + private static final float TOUCH_MAJOR = 4.4f; + private static final float TOUCH_MINOR = 5.5f; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + + @Mock + private ISession mHal; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mClientMonitorCallbackConverter; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private LockoutCache mLockoutCache; + @Mock + private IUdfpsOverlayController mUdfpsOverlayController; + @Mock + private ISidefpsController mSideFpsController; + @Mock + private FingerprintSensorPropertiesInternal mSensorProps; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Sensor.HalSessionCallback mHalSessionCallback; + @Mock + private Probe mLuxProbe; + @Captor + private ArgumentCaptor<OperationContext> mOperationContextCaptor; + @Captor + private ArgumentCaptor<PointerContext> mPointerContextCaptor; + @Captor + private ArgumentCaptor<Consumer<OperationContext>> mContextInjector; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Before + public void setup() { + when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i -> + new CallbackWithProbe<>(mLuxProbe, i.getArgument(0))); + when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer( + i -> i.getArgument(0)); + } + + @Test + public void authNoContext_v1() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + + verify(mHal).authenticate(eq(OP_ID)); + verify(mHal, never()).authenticateWithContext(anyLong(), any()); + } + + @Test + public void authWithContext_v2() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(2); + client.start(mCallback); + + InOrder order = inOrder(mHal, mBiometricContext); + order.verify(mBiometricContext).updateContext( + mOperationContextCaptor.capture(), anyBoolean()); + order.verify(mHal).authenticateWithContext( + eq(OP_ID), same(mOperationContextCaptor.getValue())); + verify(mHal, never()).authenticate(anyLong()); + } + + @Test + public void pointerUp_v1() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + client.onPointerUp(); + + verify(mHal).onPointerUp(eq(POINTER_ID)); + verify(mHal, never()).onPointerUpWithContext(any()); + } + + @Test + public void pointerDown_v1() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); + + verify(mHal).onPointerDown(eq(0), + eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR)); + verify(mHal, never()).onPointerDownWithContext(any()); + } + + @Test + public void pointerUpWithContext_v2() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(2); + client.start(mCallback); + client.onPointerUp(); + + verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture()); + verify(mHal, never()).onPointerUp(eq(POINTER_ID)); + + final PointerContext pContext = mPointerContextCaptor.getValue(); + assertThat(pContext.pointerId).isEqualTo(POINTER_ID); + } + + @Test + public void pointerDownWithContext_v2() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(2); + client.start(mCallback); + client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); + + verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture()); + verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat()); + + final PointerContext pContext = mPointerContextCaptor.getValue(); + assertThat(pContext.pointerId).isEqualTo(POINTER_ID); + } + + @Test + public void luxProbeWhenFingerDown() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(); + client.start(mCallback); + + client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); + verify(mLuxProbe).enable(); + + client.onAcquired(2, 0); + verify(mLuxProbe, never()).disable(); + + client.onPointerUp(); + verify(mLuxProbe).disable(); + + client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); + verify(mLuxProbe, times(2)).enable(); + } + + @Test + public void notifyHalWhenContextChanges() throws RemoteException { + final FingerprintAuthenticationClient client = createClient(); + client.start(mCallback); + + verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture()); + OperationContext opContext = mOperationContextCaptor.getValue(); + + // fake an update to the context + verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture()); + mContextInjector.getValue().accept(opContext); + verify(mHal).onContextChanged(eq(opContext)); + + client.stopHalOperation(); + verify(mBiometricContext).unsubscribe(same(opContext)); + } + + @Test + public void showHideOverlay_cancel() throws RemoteException { + showHideOverlay(c -> c.cancel()); + } + + @Test + public void showHideOverlay_stop() throws RemoteException { + showHideOverlay(c -> c.stopHalOperation()); + } + + @Test + public void showHideOverlay_error() throws RemoteException { + showHideOverlay(c -> c.onError(0, 0)); + verify(mCallback).onClientFinished(any(), eq(false)); + } + + @Test + public void showHideOverlay_lockout() throws RemoteException { + showHideOverlay(c -> c.onLockoutTimed(5000)); + } + + @Test + public void showHideOverlay_lockoutPerm() throws RemoteException { + showHideOverlay(c -> c.onLockoutPermanent()); + } + + private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block) + throws RemoteException { + final FingerprintAuthenticationClient client = createClient(); + + client.start(mCallback); + + verify(mUdfpsOverlayController).showUdfpsOverlay(anyInt(), anyInt(), any()); + verify(mSideFpsController).show(anyInt(), anyInt()); + + block.accept(client); + + verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt()); + verify(mSideFpsController).hide(anyInt()); + } + + private FingerprintAuthenticationClient createClient() throws RemoteException { + return createClient(100); + } + + private FingerprintAuthenticationClient createClient(int version) throws RemoteException { + when(mHal.getInterfaceVersion()).thenReturn(version); + + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken, + 2 /* requestId */, mClientMonitorCallbackConverter, 5 /* targetUserId */, OP_ID, + false /* restricted */, "test-owner", 4 /* cookie */, false /* requireConfirmation */, + 9 /* sensorId */, mBiometricLogger, mBiometricContext, + true /* isStrongBiometric */, + null /* taskStackListener */, mLockoutCache, + mUdfpsOverlayController, mSideFpsController, + false /* allowBackgroundAuthentication */, mSensorProps); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java new file mode 100644 index 000000000000..93cbef19aca9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.IUdfpsOverlayController; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.testing.TestableContext; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintDetectClientTest { + + private static final int USER_ID = 8; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + + @Mock + private ISession mHal; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mClientMonitorCallbackConverter; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private IUdfpsOverlayController mUdfpsOverlayController; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Sensor.HalSessionCallback mHalSessionCallback; + @Captor + private ArgumentCaptor<OperationContext> mOperationContextCaptor; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Before + public void setup() { + when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer( + i -> i.getArgument(0)); + } + + @Test + public void detectNoContext_v1() throws RemoteException { + final FingerprintDetectClient client = createClient(1); + + client.start(mCallback); + + verify(mHal).detectInteraction(); + verify(mHal, never()).detectInteractionWithContext(any()); + } + + @Test + public void detectNoContext_v2() throws RemoteException { + final FingerprintDetectClient client = createClient(2); + + client.start(mCallback); + + InOrder order = inOrder(mHal, mBiometricContext); + order.verify(mBiometricContext).updateContext( + mOperationContextCaptor.capture(), anyBoolean()); + order.verify(mHal).detectInteractionWithContext(same(mOperationContextCaptor.getValue())); + verify(mHal, never()).detectInteraction(); + } + + private FingerprintDetectClient createClient(int version) throws RemoteException { + when(mHal.getInterfaceVersion()).thenReturn(version); + + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + return new FingerprintDetectClient(mContext, () -> aidl, mToken, + 6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */, + "a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext, + mUdfpsOverlayController, true /* isStrongBiometric */); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java new file mode 100644 index 000000000000..5a96f5cca52a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.PointerContext; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.fingerprint.ISidefpsController; +import android.hardware.fingerprint.IUdfpsOverlayController; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.testing.TestableContext; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.log.CallbackWithProbe; +import com.android.server.biometrics.log.Probe; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.function.Consumer; + +@Presubmit +@SmallTest +public class FingerprintEnrollClientTest { + + private static final byte[] HAT = new byte[69]; + private static final int USER_ID = 8; + private static final int POINTER_ID = 0; + private static final int TOUCH_X = 8; + private static final int TOUCH_Y = 20; + private static final float TOUCH_MAJOR = 4.4f; + private static final float TOUCH_MINOR = 5.5f; + + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getTargetContext(), null); + + @Mock + private ISession mHal; + @Mock + private IBinder mToken; + @Mock + private ClientMonitorCallbackConverter mClientMonitorCallbackConverter; + @Mock + private BiometricLogger mBiometricLogger; + @Mock + private BiometricContext mBiometricContext; + @Mock + private BiometricUtils<Fingerprint> mBiometricUtils; + @Mock + private IUdfpsOverlayController mUdfpsOverlayController; + @Mock + private ISidefpsController mSideFpsController; + @Mock + private FingerprintSensorPropertiesInternal mSensorProps; + @Mock + private ClientMonitorCallback mCallback; + @Mock + private Sensor.HalSessionCallback mHalSessionCallback; + @Mock + private Probe mLuxProbe; + @Captor + private ArgumentCaptor<OperationContext> mOperationContextCaptor; + @Captor + private ArgumentCaptor<PointerContext> mPointerContextCaptor; + @Captor + private ArgumentCaptor<Consumer<OperationContext>> mContextInjector; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Before + public void setup() { + when(mBiometricLogger.createALSCallback(anyBoolean())).thenAnswer(i -> + new CallbackWithProbe<>(mLuxProbe, i.getArgument(0))); + when(mBiometricContext.updateContext(any(), anyBoolean())).thenAnswer( + i -> i.getArgument(0)); + } + + @Test + public void enrollNoContext_v1() throws RemoteException { + final FingerprintEnrollClient client = createClient(1); + + client.start(mCallback); + + verify(mHal).enroll(any()); + verify(mHal, never()).enrollWithContext(any(), any()); + } + + @Test + public void enrollWithContext_v2() throws RemoteException { + final FingerprintEnrollClient client = createClient(2); + + client.start(mCallback); + + InOrder order = inOrder(mHal, mBiometricContext); + order.verify(mBiometricContext).updateContext( + mOperationContextCaptor.capture(), anyBoolean()); + order.verify(mHal).enrollWithContext(any(), same(mOperationContextCaptor.getValue())); + verify(mHal, never()).enroll(any()); + } + + @Test + public void pointerUp_v1() throws RemoteException { + final FingerprintEnrollClient client = createClient(1); + client.start(mCallback); + client.onPointerUp(); + + verify(mHal).onPointerUp(eq(POINTER_ID)); + verify(mHal, never()).onPointerUpWithContext(any()); + } + + @Test + public void pointerDown_v1() throws RemoteException { + final FingerprintEnrollClient client = createClient(1); + client.start(mCallback); + client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); + + verify(mHal).onPointerDown(eq(0), + eq(TOUCH_X), eq(TOUCH_Y), eq(TOUCH_MAJOR), eq(TOUCH_MINOR)); + verify(mHal, never()).onPointerDownWithContext(any()); + } + + @Test + public void pointerUpWithContext_v2() throws RemoteException { + final FingerprintEnrollClient client = createClient(2); + client.start(mCallback); + client.onPointerUp(); + + verify(mHal).onPointerUpWithContext(mPointerContextCaptor.capture()); + verify(mHal, never()).onPointerUp(eq(POINTER_ID)); + + final PointerContext pContext = mPointerContextCaptor.getValue(); + assertThat(pContext.pointerId).isEqualTo(POINTER_ID); + } + + @Test + public void pointerDownWithContext_v2() throws RemoteException { + final FingerprintEnrollClient client = createClient(2); + client.start(mCallback); + client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); + + verify(mHal).onPointerDownWithContext(mPointerContextCaptor.capture()); + verify(mHal, never()).onPointerDown(anyInt(), anyInt(), anyInt(), anyFloat(), anyFloat()); + + final PointerContext pContext = mPointerContextCaptor.getValue(); + assertThat(pContext.pointerId).isEqualTo(POINTER_ID); + } + + @Test + public void luxProbeWhenFingerDown() throws RemoteException { + final FingerprintEnrollClient client = createClient(); + client.start(mCallback); + + client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); + verify(mLuxProbe).enable(); + + client.onAcquired(2, 0); + verify(mLuxProbe, never()).disable(); + + client.onPointerUp(); + verify(mLuxProbe).disable(); + + client.onPointerDown(TOUCH_X, TOUCH_Y, TOUCH_MAJOR, TOUCH_MINOR); + verify(mLuxProbe, times(2)).enable(); + } + + @Test + public void notifyHalWhenContextChanges() throws RemoteException { + final FingerprintEnrollClient client = createClient(); + client.start(mCallback); + + verify(mHal).enrollWithContext(any(), mOperationContextCaptor.capture()); + OperationContext opContext = mOperationContextCaptor.getValue(); + + // fake an update to the context + verify(mBiometricContext).subscribe(eq(opContext), mContextInjector.capture()); + mContextInjector.getValue().accept(opContext); + verify(mHal).onContextChanged(eq(opContext)); + + client.stopHalOperation(); + verify(mBiometricContext).unsubscribe(same(opContext)); + } + + @Test + public void showHideOverlay_cancel() throws RemoteException { + showHideOverlay(c -> c.cancel()); + } + + @Test + public void showHideOverlay_stop() throws RemoteException { + showHideOverlay(c -> c.stopHalOperation()); + } + + @Test + public void showHideOverlay_error() throws RemoteException { + showHideOverlay(c -> c.onError(0, 0)); + verify(mCallback).onClientFinished(any(), eq(false)); + } + + @Test + public void showHideOverlay_result() throws RemoteException { + showHideOverlay(c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0)); + } + + private void showHideOverlay(Consumer<FingerprintEnrollClient> block) + throws RemoteException { + final FingerprintEnrollClient client = createClient(); + + client.start(mCallback); + + verify(mUdfpsOverlayController).showUdfpsOverlay(anyInt(), anyInt(), any()); + verify(mSideFpsController).show(anyInt(), anyInt()); + + block.accept(client); + + verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt()); + verify(mSideFpsController).hide(anyInt()); + } + + private FingerprintEnrollClient createClient() throws RemoteException { + return createClient(500); + } + + private FingerprintEnrollClient createClient(int version) throws RemoteException { + when(mHal.getInterfaceVersion()).thenReturn(version); + + final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); + return new FingerprintEnrollClient(mContext, () -> aidl, mToken, 6 /* requestId */, + mClientMonitorCallbackConverter, 0 /* userId */, + HAT, "owner", mBiometricUtils, 8 /* sensorId */, + mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController, + mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java index 73f1516562bc..5a1a02eb39c8 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java @@ -40,6 +40,7 @@ import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.HalClientMonitor; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -71,6 +72,8 @@ public class FingerprintProviderTest { private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher; @Mock private FingerprintStateCallback mFingerprintStateCallback; + @Mock + private BiometricContext mBiometricContext; private SensorProps[] mSensorProps; private LockoutResetDispatcher mLockoutResetDispatcher; @@ -105,7 +108,7 @@ public class FingerprintProviderTest { mFingerprintProvider = new TestableFingerprintProvider(mDaemon, mContext, mFingerprintStateCallback, mSensorProps, TAG, mLockoutResetDispatcher, - mGestureAvailabilityDispatcher); + mGestureAvailabilityDispatcher, mBiometricContext); } @SuppressWarnings("rawtypes") @@ -157,9 +160,10 @@ public class FingerprintProviderTest { @NonNull SensorProps[] props, @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { + @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher, + @NonNull BiometricContext biometricContext) { super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher, - gestureAvailabilityDispatcher); + gestureAvailabilityDispatcher, biometricContext); mDaemon = daemon; } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index 8b7b484b8462..e1a4a2d9f969 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -32,6 +32,8 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.CoexCoordinator; import com.android.server.biometrics.sensors.LockoutCache; @@ -66,6 +68,10 @@ public class SensorTest { private Sensor.HalSessionCallback.Callback mHalSessionCallback; @Mock private LockoutResetDispatcher mLockoutResetDispatcher; + @Mock + private BiometricLogger mLogger; + @Mock + private BiometricContext mBiometricContext; private final TestLooper mLooper = new TestLooper(); private final LockoutCache mLockoutCache = new LockoutCache(); @@ -102,7 +108,8 @@ public class SensorTest { mScheduler.scheduleClientMonitor(new FingerprintResetLockoutClient(mContext, () -> new AidlSession(1, mSession, USER_ID, mHalCallback), - USER_ID, TAG, SENSOR_ID, HAT, mLockoutCache, mLockoutResetDispatcher)); + USER_ID, TAG, SENSOR_ID, mLogger, mBiometricContext, HAT, mLockoutCache, + mLockoutResetDispatcher)); mLooper.dispatchAll(); verifyNotLocked(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java index f6b92097a9fb..529f994f2773 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java @@ -39,6 +39,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.LockoutResetDispatcher; import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback; @@ -70,6 +71,8 @@ public class Fingerprint21Test { private BiometricScheduler mScheduler; @Mock private FingerprintStateCallback mFingerprintStateCallback; + @Mock + private BiometricContext mBiometricContext; private LockoutResetDispatcher mLockoutResetDispatcher; private Fingerprint21 mFingerprint21; @@ -101,7 +104,7 @@ public class Fingerprint21Test { mFingerprint21 = new TestableFingerprint21(mContext, mFingerprintStateCallback, sensorProps, mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, - mHalResultController); + mHalResultController, mBiometricContext); } @Test @@ -126,9 +129,10 @@ public class Fingerprint21Test { @NonNull FingerprintSensorPropertiesInternal sensorProps, @NonNull BiometricScheduler scheduler, @NonNull Handler handler, @NonNull LockoutResetDispatcher lockoutResetDispatcher, - @NonNull HalResultController controller) { + @NonNull HalResultController controller, + @NonNull BiometricContext biometricContext) { super(context, fingerprintStateCallback, sensorProps, scheduler, handler, - lockoutResetDispatcher, controller); + lockoutResetDispatcher, controller, biometricContext); } @Override diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java index ff1b6f66ef85..83fa7ac02503 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java @@ -17,17 +17,23 @@ package com.android.server.companion.virtual; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.IInputManager; +import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; import android.os.Binder; import android.os.IBinder; import android.os.IInputConstants; import android.platform.test.annotations.Presubmit; import android.view.Display; +import android.view.DisplayInfo; import androidx.test.runner.AndroidJUnit4; @@ -46,18 +52,31 @@ public class InputControllerTest { @Mock private InputManagerInternal mInputManagerInternalMock; @Mock + private DisplayManagerInternal mDisplayManagerInternalMock; + @Mock private InputController.NativeWrapper mNativeWrapperMock; + @Mock + private IInputManager mIInputManagerMock; private InputController mInputController; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); LocalServices.removeServiceForTest(InputManagerInternal.class); LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.uniqueId = "uniqueId"; + doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt()); + LocalServices.removeServiceForTest(DisplayManagerInternal.class); + LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); + + InputManager.resetInstance(mIInputManagerMock); + doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString()); + doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString()); mInputController = new InputController(new Object(), mNativeWrapperMock); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index e36263e247bf..33540c874c0a 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -57,6 +58,7 @@ import android.os.WorkSource; import android.platform.test.annotations.Presubmit; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.DisplayInfo; import android.view.KeyEvent; import androidx.test.InstrumentationRegistry; @@ -80,6 +82,8 @@ public class VirtualDeviceManagerServiceTest { private static final int DISPLAY_ID = 2; private static final int PRODUCT_ID = 10; private static final int VENDOR_ID = 5; + private static final String UNIQUE_ID = "uniqueid"; + private static final String PHYS = "phys"; private static final int HEIGHT = 1800; private static final int WIDTH = 900; private static final Binder BINDER = new Binder("binder"); @@ -116,6 +120,12 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(InputManagerInternal.class); LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.uniqueId = UNIQUE_ID; + doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt()); + LocalServices.removeServiceForTest(DisplayManagerInternal.class); + LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); + mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); doNothing().when(mContext).enforceCallingOrSelfPermission( eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); @@ -150,11 +160,11 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.onVirtualDisplayCreatedLocked(displayId); verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), - nullable(String.class), anyInt()); + nullable(String.class), anyInt(), eq(null)); TestableLooper.get(this).processAllMessages(); verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), - nullable(String.class), eq(displayId)); + nullable(String.class), eq(displayId), eq(null)); } @Test @@ -167,7 +177,7 @@ public class VirtualDeviceManagerServiceTest { TestableLooper.get(this).processAllMessages(); verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(any(Binder.class), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), - nullable(String.class), eq(displayId)); + nullable(String.class), eq(displayId), eq(null)); } @Test @@ -186,7 +196,7 @@ public class VirtualDeviceManagerServiceTest { verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), - nullable(String.class), eq(displayId)); + nullable(String.class), eq(displayId), eq(null)); IBinder wakeLock = wakeLockCaptor.getValue(); mDeviceImpl.onVirtualDisplayRemovedLocked(displayId); @@ -202,7 +212,7 @@ public class VirtualDeviceManagerServiceTest { verify(mIPowerManagerMock, Mockito.times(1)).acquireWakeLock(wakeLockCaptor.capture(), anyInt(), nullable(String.class), nullable(String.class), nullable(WorkSource.class), - nullable(String.class), eq(displayId)); + nullable(String.class), eq(displayId), eq(null)); IBinder wakeLock = wakeLockCaptor.getValue(); // Close the VirtualDevice without first notifying it of the VirtualDisplay removal. @@ -274,7 +284,8 @@ public class VirtualDeviceManagerServiceTest { BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); - verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID); + verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID), + eq(PRODUCT_ID), anyString()); } @Test @@ -284,7 +295,8 @@ public class VirtualDeviceManagerServiceTest { BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); - verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID); + verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID), + anyString()); } @Test @@ -294,8 +306,8 @@ public class VirtualDeviceManagerServiceTest { BINDER, new Point(WIDTH, HEIGHT)); assertWithMessage("Virtual keyboard should register fd when the display matches") .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); - verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT, - WIDTH); + verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID), + eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH)); } @Test @@ -315,7 +327,7 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualKeyEvent.ACTION_UP; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1, - /* displayId= */ 1)); + /* displayId= */ 1, PHYS)); mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode) .setAction(action).build()); verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action); @@ -340,7 +352,7 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1)); + /* displayId= */ 1, PHYS)); mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() .setButtonCode(buttonCode) @@ -355,7 +367,7 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1)); + /* displayId= */ 1, PHYS)); assertThrows( IllegalStateException.class, () -> @@ -381,7 +393,7 @@ public class VirtualDeviceManagerServiceTest { final float y = 0.7f; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1)); + /* displayId= */ 1, PHYS)); mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() .setRelativeX(x).setRelativeY(y).build()); @@ -395,7 +407,7 @@ public class VirtualDeviceManagerServiceTest { final float y = 0.7f; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1)); + /* displayId= */ 1, PHYS)); assertThrows( IllegalStateException.class, () -> @@ -422,7 +434,7 @@ public class VirtualDeviceManagerServiceTest { final float y = 1f; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1)); + /* displayId= */ 1, PHYS)); mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() .setXAxisMovement(x) @@ -437,7 +449,7 @@ public class VirtualDeviceManagerServiceTest { final float y = 1f; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, - /* displayId= */ 1)); + /* displayId= */ 1, PHYS)); assertThrows( IllegalStateException.class, () -> @@ -470,7 +482,7 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualTouchEvent.ACTION_UP; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, - /* displayId= */ 1)); + /* displayId= */ 1, PHYS)); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build()); verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN, @@ -489,7 +501,7 @@ public class VirtualDeviceManagerServiceTest { final float majorAxisSize = 10.0f; mInputController.mInputDeviceDescriptors.put(BINDER, new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, - /* displayId= */ 1)); + /* displayId= */ 1, PHYS)); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType) .setPressure(pressure).setMajorAxisSize(majorAxisSize).build()); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 564c4e439d86..c877bd141220 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -28,6 +28,14 @@ import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO; import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI; import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID; import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW; +import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; @@ -101,6 +109,7 @@ import android.app.admin.WifiSsidPolicy; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -138,6 +147,9 @@ import com.android.internal.widget.LockscreenCredential; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener; +import com.android.server.pm.RestrictionsSet; +import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.UserRestrictionsUtils; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; @@ -7725,30 +7737,20 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); - int returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1); - assertThat(dpms.mOwners.hasDeviceOwner()).isTrue(); - assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED); - + assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED); initializeDpms(); - - returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1); - assertThat(dpms.mOwners.hasDeviceOwner()).isTrue(); - assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED); + assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED); } @Test - public void testSetDeviceOwnerType_asDeviceOwner_throwsExceptionWhenSetDeviceOwnerTypeAgain() + public void testSetDeviceOwnerType_asDeviceOwner_setDeviceOwnerTypeTwice_success() throws Exception { setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT); dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); - int returnedDeviceOwnerType = dpm.getDeviceOwnerType(admin1); - assertThat(dpms.mOwners.hasDeviceOwner()).isTrue(); - assertThat(returnedDeviceOwnerType).isEqualTo(DEVICE_OWNER_TYPE_FINANCED); - - assertThrows(IllegalStateException.class, - () -> dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_DEFAULT)); + assertThat(dpm.getDeviceOwnerType(admin1)).isEqualTo(DEVICE_OWNER_TYPE_FINANCED); } @Test @@ -7764,6 +7766,296 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + public void testSetUserRestriction_financeDo_invalidRestrictions_restrictionNotSet() + throws Exception { + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) { + if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) { + assertNoDeviceOwnerRestrictions(); + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.addUserRestriction(admin1, restriction)); + + verify(getServices().userManagerInternal, never()) + .setDevicePolicyUserRestrictions(anyInt(), any(), any(), anyBoolean()); + } + } + } + + @Test + public void testSetUserRestriction_financeDo_validRestrictions_setsRestriction() + throws Exception { + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + for (String restriction : UserRestrictionsUtils.USER_RESTRICTIONS) { + if (UserRestrictionsUtils.canFinancedDeviceOwnerChange(restriction)) { + assertNoDeviceOwnerRestrictions(); + dpm.addUserRestriction(admin1, restriction); + + Bundle globalRestrictions = + dpms.getDeviceOwnerAdminLocked().getGlobalUserRestrictions( + UserManagerInternal.OWNER_TYPE_DEVICE_OWNER); + RestrictionsSet localRestrictions = new RestrictionsSet(); + localRestrictions.updateRestrictions( + UserHandle.USER_SYSTEM, + dpms.getDeviceOwnerAdminLocked().getLocalUserRestrictions( + UserManagerInternal.OWNER_TYPE_DEVICE_OWNER)); + verify(getServices().userManagerInternal) + .setDevicePolicyUserRestrictions(eq(UserHandle.USER_SYSTEM), + MockUtils.checkUserRestrictions(globalRestrictions), + MockUtils.checkUserRestrictions( + UserHandle.USER_SYSTEM, localRestrictions), + eq(true)); + reset(getServices().userManagerInternal); + + dpm.clearUserRestriction(admin1, restriction); + reset(getServices().userManagerInternal); + } + } + } + + @Test + public void testSetLockTaskFeatures_financeDo_validLockTaskFeatures_lockTaskFeaturesSet() + throws Exception { + int validLockTaskFeatures = LOCK_TASK_FEATURE_SYSTEM_INFO | LOCK_TASK_FEATURE_KEYGUARD + | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_GLOBAL_ACTIONS + | LOCK_TASK_FEATURE_NOTIFICATIONS; + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + dpm.setLockTaskFeatures(admin1, validLockTaskFeatures); + + verify(getServices().iactivityTaskManager) + .updateLockTaskFeatures(eq(UserHandle.USER_SYSTEM), eq(validLockTaskFeatures)); + } + + @Test + public void testSetLockTaskFeatures_financeDo_invalidLockTaskFeatures_throwsException() + throws Exception { + int invalidLockTaskFeatures = LOCK_TASK_FEATURE_NONE | LOCK_TASK_FEATURE_OVERVIEW + | LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK; + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + // Called during setup. + verify(getServices().iactivityTaskManager).updateLockTaskFeatures(anyInt(), anyInt()); + + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.setLockTaskFeatures(admin1, invalidLockTaskFeatures)); + + verifyNoMoreInteractions(getServices().iactivityTaskManager); + } + + @Test + public void testIsUninstallBlocked_financeDo_success() throws Exception { + String packageName = "com.android.foo.package"; + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + when(getServices().ipackageManager.getBlockUninstallForUser( + eq(packageName), eq(UserHandle.USER_SYSTEM))) + .thenReturn(true); + + assertThat(dpm.isUninstallBlocked(admin1, packageName)).isTrue(); + } + + @Test + public void testSetUninstallBlocked_financeDo_success() throws Exception { + String packageName = "com.android.foo.package"; + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + dpm.setUninstallBlocked(admin1, packageName, false); + + verify(getServices().ipackageManager) + .setBlockUninstallForUser(eq(packageName), eq(false), + eq(UserHandle.USER_SYSTEM)); + } + + @Test + public void testSetUserControlDisabledPackages_financeDo_success() throws Exception { + List<String> packages = new ArrayList<>(); + packages.add("com.android.foo.package"); + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + dpm.setUserControlDisabledPackages(admin1, packages); + + verify(getServices().packageManagerInternal) + .setDeviceOwnerProtectedPackages(eq(admin1.getPackageName()), eq(packages)); + } + + @Test + public void testGetUserControlDisabledPackages_financeDo_success() throws Exception { + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + assertThat(dpm.getUserControlDisabledPackages(admin1)).isEmpty(); + } + + @Test + public void testSetOrganizationName_financeDo_success() throws Exception { + String organizationName = "Test Organization"; + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + dpm.setOrganizationName(admin1, organizationName); + + assertThat(dpm.getDeviceOwnerOrganizationName()).isEqualTo(organizationName); + } + + @Test + public void testSetShortSupportMessage_financeDo_success() throws Exception { + String supportMessage = "Test short support message"; + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + dpm.setShortSupportMessage(admin1, supportMessage); + + assertThat(dpm.getShortSupportMessage(admin1)).isEqualTo(supportMessage); + } + + @Test + public void testIsBackupServiceEnabled_financeDo_success() throws Exception { + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + when(getServices().ibackupManager.isBackupServiceActive(eq(UserHandle.USER_SYSTEM))) + .thenReturn(true); + + assertThat(dpm.isBackupServiceEnabled(admin1)).isTrue(); + } + + @Test + public void testSetBackupServiceEnabled_financeDo_success() throws Exception { + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + dpm.setBackupServiceEnabled(admin1, true); + + verify(getServices().ibackupManager) + .setBackupServiceActive(eq(UserHandle.USER_SYSTEM), eq(true)); + } + + @Test + public void testIsLockTaskPermitted_financeDo_success() throws Exception { + String packageName = "com.android.foo.package"; + mockPolicyExemptApps(packageName); + mockVendorPolicyExemptApps(); + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + assertThat(dpm.isLockTaskPermitted(packageName)).isTrue(); + } + + @Test + public void testSetLockTaskPackages_financeDo_success() throws Exception { + String[] packages = {"com.android.foo.package"}; + mockEmptyPolicyExemptApps(); + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + dpm.setLockTaskPackages(admin1, packages); + + verify(getServices().iactivityManager) + .updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(packages)); + } + + @Test + public void testAddPersistentPreferredActivity_financeDo_success() throws Exception { + IntentFilter filter = new IntentFilter(); + ComponentName target = new ComponentName(admin2.getPackageName(), "test.class"); + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + dpm.addPersistentPreferredActivity(admin1, filter, target); + + verify(getServices().ipackageManager) + .addPersistentPreferredActivity(eq(filter), eq(target), eq(UserHandle.USER_SYSTEM)); + verify(getServices().ipackageManager) + .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM)); + } + + @Test + public void testClearPackagePersistentPreferredActvities_financeDo_success() throws Exception { + String packageName = admin2.getPackageName(); + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + dpm.clearPackagePersistentPreferredActivities(admin1, packageName); + + verify(getServices().ipackageManager) + .clearPackagePersistentPreferredActivities( + eq(packageName), eq(UserHandle.USER_SYSTEM)); + verify(getServices().ipackageManager) + .flushPackageRestrictionsAsUser(eq(UserHandle.USER_SYSTEM)); + } + + @Test + public void testWipeData_financeDo_success() throws Exception { + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + when(getServices().userManager.getUserRestrictionSource( + UserManager.DISALLOW_FACTORY_RESET, + UserHandle.SYSTEM)) + .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER); + when(mMockContext.getResources() + .getString(R.string.work_profile_deleted_description_dpm_wipe)) + .thenReturn("Test string"); + + dpm.wipeData(0); + + verifyRebootWipeUserData(/* wipeEuicc= */ false); + } + + @Test + public void testIsDeviceOwnerApp_financeDo_success() throws Exception { + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + assertThat(dpm.isDeviceOwnerApp(admin1.getPackageName())).isTrue(); + } + + @Test + public void testClearDeviceOwnerApp_financeDo_success() throws Exception { + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + dpm.clearDeviceOwnerApp(admin1.getPackageName()); + + assertThat(dpm.getDeviceOwnerComponentOnAnyUser()).isNull(); + assertThat(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM)).isFalse(); + verify(mMockContext.spiedContext, times(2)) + .sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED), + eq(UserHandle.SYSTEM)); + } + + @Test + public void testSetPermissionGrantState_financeDo_notReadPhoneStatePermission_throwsException() + throws Exception { + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.setPermissionGrantState(admin1, admin1.getPackageName(), + permission.READ_CALENDAR, + DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED)); + } + + @Test + public void testSetPermissionGrantState_financeDo_grantPermissionToNonDeviceOwnerPackage_throwsException() + throws Exception { + setDeviceOwner(); + dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED); + + assertExpectException(SecurityException.class, /* messageRegex= */ null, + () -> dpm.setPermissionGrantState(admin1, "com.android.foo.package", + permission.READ_PHONE_STATE, + DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED)); + } + + @Test public void testSetUsbDataSignalingEnabled_noDeviceOwnerOrPoOfOrgOwnedDevice() { assertThrows(SecurityException.class, () -> dpm.setUsbDataSignalingEnabled(true)); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java index 15f3ed1be552..4cb46b4047b0 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java @@ -109,6 +109,14 @@ public class MockUtils { public static Bundle checkUserRestrictions(String... keys) { final Bundle expected = DpmTestUtils.newRestrictions( java.util.Objects.requireNonNull(keys)); + return checkUserRestrictions(expected); + } + + public static Bundle checkUserRestrictions(Bundle expected) { + return createUserRestrictionsBundleMatcher(expected); + } + + private static Bundle createUserRestrictionsBundleMatcher(Bundle expected) { final Matcher<Bundle> m = new BaseMatcher<Bundle>() { @Override public boolean matches(Object item) { @@ -129,6 +137,15 @@ public class MockUtils { public static RestrictionsSet checkUserRestrictions(int userId, String... keys) { final RestrictionsSet expected = DpmTestUtils.newRestrictions(userId, java.util.Objects.requireNonNull(keys)); + return checkUserRestrictions(userId, expected); + } + + public static RestrictionsSet checkUserRestrictions(int userId, RestrictionsSet expected) { + return createUserRestrictionsSetMatcher(userId, expected); + } + + private static RestrictionsSet createUserRestrictionsSetMatcher( + int userId, RestrictionsSet expected) { final Matcher<RestrictionsSet> m = new BaseMatcher<RestrictionsSet>() { @Override public boolean matches(Object item) { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java index a8f24ce5e11d..533fb2d8d214 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OverlayPackagesProviderTest.java @@ -26,6 +26,7 @@ import static android.app.admin.DevicePolicyManager.REQUIRED_APP_MANAGED_USER; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; @@ -76,6 +77,7 @@ public class OverlayPackagesProviderTest { private static final ComponentName TEST_MDM_COMPONENT_NAME = new ComponentName( TEST_DPC_PACKAGE_NAME, "pc.package.name.DeviceAdmin"); private static final int TEST_USER_ID = 123; + private static final String ROLE_HOLDER_PACKAGE_NAME = "test.role.holder.package.name"; private @Mock Resources mResources; @@ -305,6 +307,26 @@ public class OverlayPackagesProviderTest { ACTION_PROVISION_MANAGED_PROFILE, "package1", "package2", "package3"); } + @Test + public void testGetNonRequiredApps_managedProfile_roleHolder_works() { + when(mInjector.getDeviceManagerRoleHolderPackageName(any())) + .thenReturn(ROLE_HOLDER_PACKAGE_NAME); + setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME); + + verifyAppsAreNonRequired( + ACTION_PROVISION_MANAGED_PROFILE, "package1", "package2"); + } + + @Test + public void testGetNonRequiredApps_managedDevice_roleHolder_works() { + when(mInjector.getDeviceManagerRoleHolderPackageName(any())) + .thenReturn(ROLE_HOLDER_PACKAGE_NAME); + setSystemAppsWithLauncher("package1", "package2", ROLE_HOLDER_PACKAGE_NAME); + + verifyAppsAreNonRequired( + ACTION_PROVISION_MANAGED_DEVICE, "package1", "package2"); + } + private void setupRegularModulesWithManagedUser(String... regularModules) { setupRegularModulesWithMetadata(regularModules, REQUIRED_APP_MANAGED_USER); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java index 02a8ae8fbecd..9a5254d3e00d 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java @@ -93,7 +93,7 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getProfileOwnerUserRestrictionsNeedsMigration(21)).isFalse(); owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(), - DEVICE_OWNER_TYPE_FINANCED); + DEVICE_OWNER_TYPE_FINANCED, /* isAdminTestOnly= */ false); // There is no device owner, so the default owner type should be returned. assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( DEVICE_OWNER_TYPE_DEFAULT); @@ -367,7 +367,7 @@ public class OwnersTest extends DpmTestBase { owners.setDeviceOwnerUserRestrictionsMigrated(); owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(), - DEVICE_OWNER_TYPE_FINANCED); + DEVICE_OWNER_TYPE_FINANCED, /* isAdminTestOnly= */ false); assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( DEVICE_OWNER_TYPE_FINANCED); @@ -399,7 +399,7 @@ public class OwnersTest extends DpmTestBase { owners.setProfileOwnerUserRestrictionsMigrated(11); owners.setDeviceOwnerType(owners.getDeviceOwnerPackageName(), - DEVICE_OWNER_TYPE_DEFAULT); + DEVICE_OWNER_TYPE_DEFAULT, /* isAdminTestOnly= */ false); // The previous device owner type should persist. assertThat(owners.getDeviceOwnerType(owners.getDeviceOwnerPackageName())).isEqualTo( DEVICE_OWNER_TYPE_FINANCED); @@ -585,7 +585,8 @@ public class OwnersTest extends DpmTestBase { assertThat(owners.getProfileOwnerFile(11).exists()).isTrue(); String previousDeviceOwnerPackageName = owners.getDeviceOwnerPackageName(); - owners.setDeviceOwnerType(previousDeviceOwnerPackageName, DEVICE_OWNER_TYPE_FINANCED); + owners.setDeviceOwnerType(previousDeviceOwnerPackageName, DEVICE_OWNER_TYPE_FINANCED, + /* isAdminTestOnly= */ false); assertThat(owners.getDeviceOwnerType(previousDeviceOwnerPackageName)).isEqualTo( DEVICE_OWNER_TYPE_FINANCED); owners.setDeviceOwnerProtectedPackages( diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index d2cff0ea1968..fe3034dc569d 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -326,7 +326,7 @@ public final class DeviceStateManagerServiceTest { assertEquals(callback.getLastNotifiedInfo().currentState, OTHER_DEVICE_STATE.getIdentifier()); - mService.getBinderService().cancelRequest(token); + mService.getBinderService().cancelStateRequest(); flushHandler(); assertEquals(callback.getLastNotifiedStatus(token), @@ -378,9 +378,9 @@ public final class DeviceStateManagerServiceTest { mPolicy.resumeConfigureOnce(); flushHandler(); - // First request status is now suspended as there is another pending request. + // First request status is now canceled as there is another pending request. assertEquals(callback.getLastNotifiedStatus(firstRequestToken), - TestDeviceStateManagerCallback.STATUS_SUSPENDED); + TestDeviceStateManagerCallback.STATUS_CANCELED); // Second request status still unknown because the service is still awaiting policy // callback. assertEquals(callback.getLastNotifiedStatus(secondRequestToken), @@ -402,19 +402,19 @@ public final class DeviceStateManagerServiceTest { DEFAULT_DEVICE_STATE.getIdentifier()); // Now cancel the second request to make the first request active. - mService.getBinderService().cancelRequest(secondRequestToken); + mService.getBinderService().cancelStateRequest(); flushHandler(); assertEquals(callback.getLastNotifiedStatus(firstRequestToken), - TestDeviceStateManagerCallback.STATUS_ACTIVE); + TestDeviceStateManagerCallback.STATUS_CANCELED); assertEquals(callback.getLastNotifiedStatus(secondRequestToken), TestDeviceStateManagerCallback.STATUS_CANCELED); - assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE)); + assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mService.getPendingState(), Optional.empty()); assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE)); assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(), - OTHER_DEVICE_STATE.getIdentifier()); + DEFAULT_DEVICE_STATE.getIdentifier()); } @Test @@ -656,11 +656,6 @@ public final class DeviceStateManagerServiceTest { } @Override - public void onRequestSuspended(IBinder token) { - mLastNotifiedStatus.put(token, STATUS_SUSPENDED); - } - - @Override public void onRequestCanceled(IBinder token) { mLastNotifiedStatus.put(token, STATUS_CANCELED); } diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java index b94fc4308ce2..2297c91818c0 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java @@ -18,7 +18,6 @@ package com.android.server.devicestate; import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE; import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED; -import static com.android.server.devicestate.OverrideRequestController.STATUS_SUSPENDED; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; @@ -66,7 +65,7 @@ public final class OverrideRequestControllerTest { } @Test - public void addRequest_suspendExistingRequest() { + public void addRequest_cancelExistingRequestThroughNewRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* requestedState */, 0 /* flags */); assertNull(mStatusListener.getLastStatus(firstRequest)); @@ -75,92 +74,52 @@ public final class OverrideRequestControllerTest { assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + 1 /* requestedState */, 0 /* flags */); assertNull(mStatusListener.getLastStatus(secondRequest)); mController.addRequest(secondRequest); assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); } @Test public void addRequest_cancelActiveRequest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* requestedState */, 0 /* flags */); - OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); mController.addRequest(firstRequest); - mController.addRequest(secondRequest); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); - mController.cancelRequest(secondRequest.getToken()); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); - } - @Test - public void addRequest_cancelSuspendedRequest() { - OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); - OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); + mController.cancelOverrideRequest(); - mController.addRequest(firstRequest); - mController.addRequest(secondRequest); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); - - mController.cancelRequest(firstRequest.getToken()); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); } @Test public void handleBaseStateChanged() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 0 /* requestedState */, 0 /* flags */); - OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* requestedState */, DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */); mController.addRequest(firstRequest); - mController.addRequest(secondRequest); - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); mController.handleBaseStateChanged(); - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); } @Test public void handleProcessDied() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* requestedState */, 0 /* flags */); - OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */, - 0 /* requestedState */, 0 /* flags */); mController.addRequest(firstRequest); - mController.addRequest(secondRequest); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); - - mController.handleProcessDied(1); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); mController.handleProcessDied(0); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); } @@ -170,46 +129,29 @@ public final class OverrideRequestControllerTest { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* requestedState */, 0 /* flags */); - OverrideRequest secondRequest = new OverrideRequest(new Binder(), 1 /* pid */, - 0 /* requestedState */, 0 /* flags */); mController.addRequest(firstRequest); - mController.addRequest(secondRequest); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); - - mController.handleProcessDied(1); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); - - mController.cancelStickyRequests(); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); + mController.handleProcessDied(0); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); + + mController.cancelStickyRequest(); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); } @Test public void handleNewSupportedStates() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 1 /* requestedState */, 0 /* flags */); - OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 2 /* requestedState */, 0 /* flags */); mController.addRequest(firstRequest); - mController.addRequest(secondRequest); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); mController.handleNewSupportedStates(new int[]{ 0, 1 }); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); mController.handleNewSupportedStates(new int[]{ 0 }); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); } @@ -217,18 +159,11 @@ public final class OverrideRequestControllerTest { public void cancelOverrideRequestsTest() { OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 1 /* requestedState */, 0 /* flags */); - OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, - 2 /* requestedState */, 0 /* flags */); mController.addRequest(firstRequest); - mController.addRequest(secondRequest); - - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_SUSPENDED); - - mController.cancelOverrideRequests(); + assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE); - assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_CANCELED); + mController.cancelOverrideRequest(); assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED); } 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 4caa85cee7db..f5a56891b5f5 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -81,6 +81,7 @@ public class AutomaticBrightnessControllerTest { @Mock HysteresisLevels mScreenBrightnessThresholds; @Mock Handler mNoOpHandler; @Mock HighBrightnessModeController mHbmController; + @Mock BrightnessThrottler mBrightnessThrottler; @Before public void setUp() { @@ -128,12 +129,15 @@ public class AutomaticBrightnessControllerTest { INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG, DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG, mAmbientBrightnessThresholds, mScreenBrightnessThresholds, - mContext, mHbmController, mIdleBrightnessMappingStrategy, + mContext, mHbmController, mBrightnessThrottler, mIdleBrightnessMappingStrategy, AMBIENT_LIGHT_HORIZON_SHORT, AMBIENT_LIGHT_HORIZON_LONG ); when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT); when(mHbmController.getCurrentBrightnessMin()).thenReturn(BRIGHTNESS_MIN_FLOAT); + // Disable brightness throttling by default. Individual tests can enable it as needed. + when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); + when(mBrightnessThrottler.isThrottled()).thenReturn(false); // Configure the brightness controller and grab an instance of the sensor listener, // through which we can deliver fake (for test) sensor values. @@ -420,4 +424,47 @@ public class AutomaticBrightnessControllerTest { assertEquals(600f, hysteresisLevels.getBrighteningThreshold(500f), EPSILON); assertEquals(250f, hysteresisLevels.getDarkeningThreshold(500f), EPSILON); } + + @Test + public void testBrightnessGetsThrottled() throws Exception { + Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor"); + mController = setupController(lightSensor); + + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Set up system to return max brightness at 100 lux + final float normalizedBrightness = BRIGHTNESS_MAX_FLOAT; + final float lux = 100.0f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)) + .thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)) + .thenReturn(lux); + when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt())) + .thenReturn(normalizedBrightness); + + // Sensor reads 100 lux. We should get max brightness. + listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux)); + assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f); + + // Apply throttling and notify ABC (simulates DisplayPowerController#updatePowerState()) + final float throttledBrightness = 0.123f; + when(mBrightnessThrottler.getBrightnessCap()).thenReturn(throttledBrightness); + when(mBrightnessThrottler.isThrottled()).thenReturn(true); + mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */, + BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */, + 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT); + assertEquals(throttledBrightness, mController.getAutomaticScreenBrightness(), 0.0f); + + // Remove throttling and notify ABC again + when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); + when(mBrightnessThrottler.isThrottled()).thenReturn(false); + mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration */, + BRIGHTNESS_MAX_FLOAT /* brightness */, false /* userChangedBrightness */, + 0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT); + assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f); + } } diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java index b7af010103bc..6203c2f54f07 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -663,6 +663,30 @@ public class HighBrightnessModeControllerTest { eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING)); } + @Test + public void tetHbmStats_LowRequestedBrightness() { + final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); + final int displayStatsId = mDisplayUniqueId.hashCode(); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); + advanceTime(0); + // verify in HBM sunlight mode + assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); + // verify HBM_ON_SUNLIGHT + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + + hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN); + // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS)); + } + private void assertState(HighBrightnessModeController hbmc, float brightnessMin, float brightnessMax, int hbmMode) { assertEquals(brightnessMin, hbmc.getCurrentBrightnessMin(), EPSILON); diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java index 0f3742f8577d..ac97911027bf 100644 --- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java +++ b/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java @@ -59,6 +59,8 @@ import java.util.List; @RunWith(JUnit4.class) public final class AmbientLuxTest { + + private static final float ALLOWED_ERROR_DELTA = 0.001f; private static final int AMBIENT_COLOR_TYPE = 20705; private static final String AMBIENT_COLOR_TYPE_STR = "colorSensoryDensoryDoc"; private static final float LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE = 5432.1f; @@ -78,6 +80,8 @@ public final class AmbientLuxTest { @Mock private TypedArray mHighLightBiases; @Mock private TypedArray mAmbientColorTemperatures; @Mock private TypedArray mDisplayColorTemperatures; + @Mock private TypedArray mStrongAmbientColorTemperatures; + @Mock private TypedArray mStrongDisplayColorTemperatures; @Mock private ColorDisplayService.ColorDisplayServiceInternal mColorDisplayServiceInternalMock; @Before @@ -110,6 +114,12 @@ public final class AmbientLuxTest { when(mResourcesSpy.obtainTypedArray( R.array.config_displayWhiteBalanceDisplayColorTemperatures)) .thenReturn(mDisplayColorTemperatures); + when(mResourcesSpy.obtainTypedArray( + R.array.config_displayWhiteBalanceStrongAmbientColorTemperatures)) + .thenReturn(mStrongAmbientColorTemperatures); + when(mResourcesSpy.obtainTypedArray( + R.array.config_displayWhiteBalanceStrongDisplayColorTemperatures)) + .thenReturn(mStrongDisplayColorTemperatures); when(mResourcesSpy.obtainTypedArray( R.array.config_displayWhiteBalanceLowLightAmbientBrightnesses)) @@ -375,6 +385,43 @@ public final class AmbientLuxTest { } @Test + public void testStrongMode() { + final float lowerBrightness = 10.0f; + final float upperBrightness = 50.0f; + setBrightnesses(lowerBrightness, upperBrightness); + setBiases(0.0f, 1.0f); + final int ambientColorTempLow = 6000; + final int ambientColorTempHigh = 8000; + final int displayColorTempLow = 6400; + final int displayColorTempHigh = 7400; + setStrongAmbientColorTemperatures(ambientColorTempLow, ambientColorTempHigh); + setStrongDisplayColorTemperatures(displayColorTempLow, displayColorTempHigh); + + DisplayWhiteBalanceController controller = + DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy); + controller.setStrongModeEnabled(true); + controller.mBrightnessFilter = spy(new AmbientFilterStubber()); + + for (float ambientTempFraction = 0.0f; ambientTempFraction <= 1.0f; + ambientTempFraction += 0.1f) { + final float ambientTemp = + (ambientColorTempHigh - ambientColorTempLow) * ambientTempFraction + + ambientColorTempLow; + setEstimatedColorTemperature(controller, ambientTemp); + for (float brightnessFraction = 0.0f; brightnessFraction <= 1.0f; + brightnessFraction += 0.1f) { + setEstimatedBrightnessAndUpdate(controller, + mix(lowerBrightness, upperBrightness, brightnessFraction)); + assertEquals(controller.mPendingAmbientColorTemperature, + mix(LOW_LIGHT_AMBIENT_COLOR_TEMPERATURE, + mix(displayColorTempLow, displayColorTempHigh, ambientTempFraction), + brightnessFraction), + ALLOWED_ERROR_DELTA); + } + } + } + + @Test public void testLowLight_DefaultAmbient() throws Exception { final float lowerBrightness = 10.0f; final float upperBrightness = 50.0f; @@ -486,6 +533,14 @@ public final class AmbientLuxTest { setFloatArrayResource(mDisplayColorTemperatures, vals); } + private void setStrongAmbientColorTemperatures(float... vals) { + setFloatArrayResource(mStrongAmbientColorTemperatures, vals); + } + + private void setStrongDisplayColorTemperatures(float... vals) { + setFloatArrayResource(mStrongDisplayColorTemperatures, vals); + } + private void setFloatArrayResource(TypedArray array, float[] vals) { when(array.length()).thenReturn(vals.length); for (int i = 0; i < vals.length; i++) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 7751ef564138..c48a974ddbb2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -42,6 +42,7 @@ import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener; import android.hardware.hdmi.IHdmiControlStatusChangeListener; +import android.hardware.hdmi.IHdmiVendorCommandListener; import android.os.Binder; import android.os.Looper; import android.os.RemoteException; @@ -747,6 +748,114 @@ public class HdmiControlServiceTest { } @Test + public void addVendorCommandListener_receiveCallback_VendorCmdNoIdTest() { + int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(); + int sourceAddress = Constants.ADDR_TV; + byte[] params = {0x00, 0x01, 0x02, 0x03}; + int vendorId = 0x123456; + + VendorCommandListener vendorCmdListener = + new VendorCommandListener(sourceAddress, destAddress, params, vendorId); + mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId); + mTestLooper.dispatchAll(); + + HdmiCecMessage vendorCommandNoId = + HdmiCecMessageBuilder.buildVendorCommand(sourceAddress, destAddress, params); + mNativeWrapper.onCecMessage(vendorCommandNoId); + mTestLooper.dispatchAll(); + assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isTrue(); + assertThat(vendorCmdListener.mParamsCorrect).isTrue(); + assertThat(vendorCmdListener.mHasVendorId).isFalse(); + } + + @Test + public void addVendorCommandListener_receiveCallback_VendorCmdWithIdTest() { + int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(); + int sourceAddress = Constants.ADDR_TV; + byte[] params = {0x00, 0x01, 0x02, 0x03}; + int vendorId = 0x123456; + + VendorCommandListener vendorCmdListener = + new VendorCommandListener(sourceAddress, destAddress, params, vendorId); + mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId); + mTestLooper.dispatchAll(); + + HdmiCecMessage vendorCommandWithId = + HdmiCecMessageBuilder.buildVendorCommandWithId( + sourceAddress, destAddress, vendorId, params); + mNativeWrapper.onCecMessage(vendorCommandWithId); + mTestLooper.dispatchAll(); + assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isTrue(); + assertThat(vendorCmdListener.mParamsCorrect).isTrue(); + assertThat(vendorCmdListener.mHasVendorId).isTrue(); + } + + @Test + public void addVendorCommandListener_noCallback_VendorCmdDiffIdTest() { + int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(); + int sourceAddress = Constants.ADDR_TV; + byte[] params = {0x00, 0x01, 0x02, 0x03}; + int vendorId = 0x123456; + int diffVendorId = 0x345678; + + VendorCommandListener vendorCmdListener = + new VendorCommandListener(sourceAddress, destAddress, params, vendorId); + mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId); + mTestLooper.dispatchAll(); + + HdmiCecMessage vendorCommandWithDiffId = + HdmiCecMessageBuilder.buildVendorCommandWithId( + sourceAddress, destAddress, diffVendorId, params); + mNativeWrapper.onCecMessage(vendorCommandWithDiffId); + mTestLooper.dispatchAll(); + assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isFalse(); + } + + private static class VendorCommandListener extends IHdmiVendorCommandListener.Stub { + boolean mVendorCommandCallbackReceived = false; + boolean mParamsCorrect = false; + boolean mHasVendorId = false; + + int mSourceAddress; + int mDestAddress; + byte[] mParams; + int mVendorId; + + VendorCommandListener(int sourceAddress, int destAddress, byte[] params, int vendorId) { + this.mSourceAddress = sourceAddress; + this.mDestAddress = destAddress; + this.mParams = params.clone(); + this.mVendorId = vendorId; + } + + @Override + public void onReceived( + int sourceAddress, int destAddress, byte[] params, boolean hasVendorId) { + mVendorCommandCallbackReceived = true; + if (mSourceAddress == sourceAddress && mDestAddress == destAddress) { + byte[] expectedParams; + if (hasVendorId) { + // If the command has vendor ID, we have to add it to mParams. + expectedParams = new byte[params.length]; + expectedParams[0] = (byte) ((mVendorId >> 16) & 0xFF); + expectedParams[1] = (byte) ((mVendorId >> 8) & 0xFF); + expectedParams[2] = (byte) (mVendorId & 0xFF); + System.arraycopy(mParams, 0, expectedParams, 3, mParams.length); + } else { + expectedParams = params.clone(); + } + if (Arrays.equals(expectedParams, params)) { + mParamsCorrect = true; + } + } + mHasVendorId = hasVendorId; + } + + @Override + public void onControlStateChanged(boolean enabled, int reason) {} + } + + @Test public void dispatchMessageToLocalDevice_broadcastMessage_returnsHandled() { HdmiCecMessage message = HdmiCecMessageBuilder.buildStandby( Constants.ADDR_TV, diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java index b621a4408f40..869ac8877d6d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerTests.java @@ -70,7 +70,7 @@ import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import com.android.frameworks.servicestests.R; -import com.android.internal.content.PackageHelper; +import com.android.internal.content.InstallLocationUtils; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; @@ -106,11 +106,11 @@ public class PackageManagerTests extends AndroidTestCase { private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec"; - private static final int APP_INSTALL_AUTO = PackageHelper.APP_INSTALL_AUTO; + private static final int APP_INSTALL_AUTO = InstallLocationUtils.APP_INSTALL_AUTO; - private static final int APP_INSTALL_DEVICE = PackageHelper.APP_INSTALL_INTERNAL; + private static final int APP_INSTALL_DEVICE = InstallLocationUtils.APP_INSTALL_INTERNAL; - private static final int APP_INSTALL_SDCARD = PackageHelper.APP_INSTALL_EXTERNAL; + private static final int APP_INSTALL_SDCARD = InstallLocationUtils.APP_INSTALL_EXTERNAL; void failStr(String errMsg) { Log.w(TAG, "errMsg=" + errMsg); @@ -1214,7 +1214,7 @@ public class PackageManagerTests extends AndroidTestCase { int origDefaultLoc = getDefaultInstallLoc(); InstallParams ip = null; try { - setInstallLoc(PackageHelper.APP_INSTALL_AUTO); + setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO); // Install first ip = installFromRawResource("install.apk", rawResId, installFlags, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); @@ -1303,7 +1303,7 @@ public class PackageManagerTests extends AndroidTestCase { InstallParams ip = null; try { PackageManager pm = getPm(); - setInstallLoc(PackageHelper.APP_INSTALL_AUTO); + setInstallLoc(InstallLocationUtils.APP_INSTALL_AUTO); // Install first ip = installFromRawResource("install.apk", R.raw.install, installFlags, false, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); @@ -1517,11 +1517,11 @@ public class PackageManagerTests extends AndroidTestCase { int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; boolean enable = getUserSettingSetInstallLocation(); if (enable) { - if (userSetting == PackageHelper.APP_INSTALL_AUTO) { + if (userSetting == InstallLocationUtils.APP_INSTALL_AUTO) { iloc = PackageInfo.INSTALL_LOCATION_AUTO; - } else if (userSetting == PackageHelper.APP_INSTALL_EXTERNAL) { + } else if (userSetting == InstallLocationUtils.APP_INSTALL_EXTERNAL) { iloc = PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL; - } else if (userSetting == PackageHelper.APP_INSTALL_INTERNAL) { + } else if (userSetting == InstallLocationUtils.APP_INSTALL_INTERNAL) { iloc = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; } } @@ -1552,7 +1552,7 @@ public class PackageManagerTests extends AndroidTestCase { } @LargeTest public void testExistingIUserI() throws Exception { - int userSetting = PackageHelper.APP_INSTALL_INTERNAL; + int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL; int iFlags = PackageManager.INSTALL_INTERNAL; setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } @@ -1564,14 +1564,14 @@ public class PackageManagerTests extends AndroidTestCase { return; } - int userSetting = PackageHelper.APP_INSTALL_EXTERNAL; + int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL; int iFlags = PackageManager.INSTALL_INTERNAL; setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } @LargeTest public void testExistingIUserA() throws Exception { - int userSetting = PackageHelper.APP_INSTALL_AUTO; + int userSetting = InstallLocationUtils.APP_INSTALL_AUTO; int iFlags = PackageManager.INSTALL_INTERNAL; setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } @@ -1616,7 +1616,7 @@ public class PackageManagerTests extends AndroidTestCase { } @LargeTest public void testUserI() throws Exception { - int userSetting = PackageHelper.APP_INSTALL_INTERNAL; + int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL; int iloc = getExpectedInstallLocation(userSetting); setUserX(true, userSetting, iloc); } @@ -1628,14 +1628,14 @@ public class PackageManagerTests extends AndroidTestCase { return; } - int userSetting = PackageHelper.APP_INSTALL_EXTERNAL; + int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL; int iloc = getExpectedInstallLocation(userSetting); setUserX(true, userSetting, iloc); } @LargeTest public void testUserA() throws Exception { - int userSetting = PackageHelper.APP_INSTALL_AUTO; + int userSetting = InstallLocationUtils.APP_INSTALL_AUTO; int iloc = getExpectedInstallLocation(userSetting); setUserX(true, userSetting, iloc); } @@ -1646,7 +1646,7 @@ public class PackageManagerTests extends AndroidTestCase { */ @LargeTest public void testUserPrefOffUserI() throws Exception { - int userSetting = PackageHelper.APP_INSTALL_INTERNAL; + int userSetting = InstallLocationUtils.APP_INSTALL_INTERNAL; int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; setUserX(false, userSetting, iloc); } @@ -1658,14 +1658,14 @@ public class PackageManagerTests extends AndroidTestCase { return; } - int userSetting = PackageHelper.APP_INSTALL_EXTERNAL; + int userSetting = InstallLocationUtils.APP_INSTALL_EXTERNAL; int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; setUserX(false, userSetting, iloc); } @LargeTest public void testUserPrefOffA() throws Exception { - int userSetting = PackageHelper.APP_INSTALL_AUTO; + int userSetting = InstallLocationUtils.APP_INSTALL_AUTO; int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; setUserX(false, userSetting, iloc); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java index 4a24bbdb09ac..8e53ca1d599d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java @@ -17,7 +17,7 @@ package com.android.server.pm; import static android.content.pm.SharedLibraryInfo.TYPE_DYNAMIC; -import static android.content.pm.SharedLibraryInfo.TYPE_SDK; +import static android.content.pm.SharedLibraryInfo.TYPE_SDK_PACKAGE; import static android.content.pm.SharedLibraryInfo.TYPE_STATIC; import static android.content.pm.SharedLibraryInfo.VERSION_UNDEFINED; @@ -258,7 +258,7 @@ public class ScanTests { assertThat(scanResult.mSdkSharedLibraryInfo.getPackageName(), is("ogl.sdk_123")); assertThat(scanResult.mSdkSharedLibraryInfo.getName(), is("ogl.sdk")); assertThat(scanResult.mSdkSharedLibraryInfo.getLongVersion(), is(123L)); - assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK)); + assertThat(scanResult.mSdkSharedLibraryInfo.getType(), is(TYPE_SDK_PACKAGE)); assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getPackageName(), is("ogl.sdk_123")); assertThat(scanResult.mSdkSharedLibraryInfo.getDeclaringPackage().getLongVersionCode(), diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index 51dbd97c183b..827349ad433a 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -64,6 +64,7 @@ import android.os.BatterySaverPolicyConfig; import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.os.IWakeLockCallback; import android.os.Looper; import android.os.PowerManager; import android.os.PowerSaveState; @@ -102,6 +103,7 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; @@ -479,21 +481,21 @@ public class PowerManagerServiceTest { // First, ensure that a normal full wake lock does not cause a wakeup int flags = PowerManager.FULL_WAKE_LOCK; mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, - null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY); + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */); // Ensure that the flag does *NOT* work with a partial wake lock. flags = PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP; mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, - null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY); + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP); mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */); // Verify that flag forces a wakeup when paired to a FULL_WAKE_LOCK flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP; mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, - null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY); + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */); } @@ -661,12 +663,12 @@ public class PowerManagerServiceTest { wakelockMap.put((String) inv.getArguments()[1], (int) inv.getArguments()[0]); return null; }).when(mNotifierMock).onWakeLockAcquired(anyInt(), anyString(), anyString(), anyInt(), - anyInt(), any(), any()); + anyInt(), any(), any(), any()); doAnswer(inv -> { wakelockMap.remove((String) inv.getArguments()[1]); return null; }).when(mNotifierMock).onWakeLockReleased(anyInt(), anyString(), anyString(), anyInt(), - anyInt(), any(), any()); + anyInt(), any(), any(), any()); // // TEST STARTS HERE @@ -679,7 +681,7 @@ public class PowerManagerServiceTest { // Create a wakelock mService.getBinderServiceInstance().acquireWakeLock(new Binder(), flags, tag, pkg, - null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY); + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); assertThat(wakelockMap.get(tag)).isEqualTo(flags); // Verify wakelock is active. // Confirm that the wakelocks have been disabled when the forceSuspend is in flight. @@ -737,7 +739,7 @@ public class PowerManagerServiceTest { // Take a nap and verify we no longer hold the blocker int flags = PowerManager.DOZE_WAKE_LOCK; mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, - null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY); + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); mService.getBinderServiceInstance().goToSleep(mClock.now(), PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0); @@ -893,7 +895,7 @@ public class PowerManagerServiceTest { mService.getBinderServiceInstance().acquireWakeLock(token, PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg, - null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY); + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); advanceTime(60); @@ -919,7 +921,7 @@ public class PowerManagerServiceTest { mService.getBinderServiceInstance().acquireWakeLock(token, PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, tag, pkg, - null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY); + null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null); advanceTime(1500); mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */); @@ -995,7 +997,7 @@ public class PowerManagerServiceTest { mService.getBinderServiceInstance().acquireWakeLock(token, PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg, - null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY); + null /* workSource */, null /* historyTag */, Display.DEFAULT_DISPLAY, null); assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( @@ -1035,7 +1037,7 @@ public class PowerManagerServiceTest { mService.getBinderServiceInstance().acquireWakeLock(token, PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg, - null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY); + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null); assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( @@ -1076,7 +1078,7 @@ public class PowerManagerServiceTest { mService.getBinderServiceInstance().acquireWakeLock(token, PowerManager.SCREEN_BRIGHT_WAKE_LOCK, tag, pkg, - null /* workSource */, null /* historyTag */, nonDefaultDisplay); + null /* workSource */, null /* historyTag */, nonDefaultDisplay, null); assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( WAKEFULNESS_AWAKE); @@ -1640,7 +1642,65 @@ public class PowerManagerServiceTest { IBinder token = new Binder(); String packageName = "pkg.name"; mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, - null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY); + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, + null /* callback */); return mService.findWakeLockLocked(token); } + + /** + * Test IPowerManager.acquireWakeLock() with a IWakeLockCallback. + */ + @Test + public void testNotifyWakeLockCallback() { + createService(); + startSystem(); + final String tag = "wakelock1"; + final String packageName = "pkg.name"; + final IBinder token = new Binder(); + final int flags = PowerManager.PARTIAL_WAKE_LOCK; + final IWakeLockCallback callback = Mockito.mock(IWakeLockCallback.class); + final IBinder callbackBinder = Mockito.mock(Binder.class); + when(callback.asBinder()).thenReturn(callbackBinder); + mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback); + verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(), + anyInt(), any(), any(), same(callback)); + + mService.getBinderServiceInstance().releaseWakeLock(token, 0); + verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(), + anyInt(), any(), any(), same(callback)); + } + + /** + * Test IPowerManager.updateWakeLockCallback() with a new IWakeLockCallback. + */ + @Test + public void testNotifyWakeLockCallbackChange() { + createService(); + startSystem(); + final String tag = "wakelock1"; + final String packageName = "pkg.name"; + final IBinder token = new Binder(); + int flags = PowerManager.PARTIAL_WAKE_LOCK; + final IWakeLockCallback callback1 = Mockito.mock(IWakeLockCallback.class); + final IBinder callbackBinder1 = Mockito.mock(Binder.class); + when(callback1.asBinder()).thenReturn(callbackBinder1); + mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, callback1); + verify(mNotifierMock).onWakeLockAcquired(anyInt(), eq(tag), eq(packageName), anyInt(), + anyInt(), any(), any(), same(callback1)); + + final IWakeLockCallback callback2 = Mockito.mock(IWakeLockCallback.class); + final IBinder callbackBinder2 = Mockito.mock(Binder.class); + when(callback2.asBinder()).thenReturn(callbackBinder2); + mService.getBinderServiceInstance().updateWakeLockCallback(token, callback2); + verify(mNotifierMock).onWakeLockChanging(anyInt(), eq(tag), eq(packageName), + anyInt(), anyInt(), any(), any(), same(callback1), + anyInt(), eq(tag), eq(packageName), anyInt(), anyInt(), any(), any(), + same(callback2)); + + mService.getBinderServiceInstance().releaseWakeLock(token, 0); + verify(mNotifierMock).onWakeLockReleased(anyInt(), eq(tag), eq(packageName), anyInt(), + anyInt(), any(), any(), same(callback2)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java index 5eed30be9279..91d4f8f63f38 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java @@ -67,6 +67,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { @Mock Vibrator mVibrator; private final String callPkg = "com.android.server.notification"; + private final String sysPkg = "android"; private final int callUid = 10; private String smsPkg; private final int smsUid = 11; @@ -79,6 +80,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { private NotificationRecord mRecordHighCall; private NotificationRecord mRecordHighCallStyle; private NotificationRecord mRecordEmail; + private NotificationRecord mRecordSystemMax; private NotificationRecord mRecordInlineReply; private NotificationRecord mRecordSms; private NotificationRecord mRecordStarredContact; @@ -191,6 +193,12 @@ public class NotificationComparatorTest extends UiServiceTestCase { mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT); mRecordContact.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT); + Notification nSystemMax = new Notification.Builder(mContext, TEST_CHANNEL_ID).build(); + mRecordSystemMax = new NotificationRecord(mContext, new StatusBarNotification(sysPkg, + sysPkg, 1, "systemmax", uid2, uid2, nSystemMax, new UserHandle(userId), + "", 1244), getDefaultChannel()); + mRecordSystemMax.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); + Notification n8 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build(); mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2, pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId), @@ -267,6 +275,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { } expected.add(mRecordStarredContact); expected.add(mRecordContact); + expected.add(mRecordSystemMax); expected.add(mRecordEmail); expected.add(mRecordUrgent); expected.add(mNoMediaSessionMedia); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java index fa294dd61ea3..3b6718207c83 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java @@ -20,8 +20,8 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET; import static android.content.pm.PackageManager.GET_PERMISSIONS; -import static android.permission.PermissionManager.PERMISSION_GRANTED; -import static android.permission.PermissionManager.PERMISSION_SOFT_DENIED; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static com.google.common.truth.Truth.assertThat; @@ -130,13 +130,13 @@ public class PermissionHelperTest extends UiServiceTestCase { @Test public void testHasPermission() throws Exception { - when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS))) + when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt())) .thenReturn(PERMISSION_GRANTED); assertThat(mPermissionHelper.hasPermission(1)).isTrue(); - when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS))) - .thenReturn(PERMISSION_SOFT_DENIED); + when(mPmi.checkPostNotificationsPermissionGrantedOrLegacyAccess(anyInt())) + .thenReturn(PERMISSION_DENIED); assertThat(mPermissionHelper.hasPermission(1)).isFalse(); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java index fb1508842c9d..0f18cc61a95a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java @@ -17,7 +17,6 @@ package com.android.server.notification; import static android.app.Notification.CATEGORY_CALL; -import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE; import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT; @@ -25,6 +24,7 @@ import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_NONE; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES; +import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; import static android.provider.Settings.Global.ZEN_MODE_ALARMS; @@ -43,8 +43,10 @@ import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager.Policy; import android.media.AudioAttributes; +import android.os.Bundle; import android.os.UserHandle; import android.service.notification.StatusBarNotification; +import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -68,10 +70,15 @@ public class ZenModeFilteringTest extends UiServiceTestCase { private NotificationMessagingUtil mMessagingUtil; private ZenModeFiltering mZenModeFiltering; + @Mock private TelephonyManager mTelephonyManager; + @Before public void setUp() { MockitoAnnotations.initMocks(this); mZenModeFiltering = new ZenModeFiltering(mContext, mMessagingUtil); + + // for repeat callers / matchesCallFilter + mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager); } private NotificationRecord getNotificationRecord() { @@ -95,6 +102,23 @@ public class ZenModeFilteringTest extends UiServiceTestCase { return r; } + private Bundle makeExtrasBundleWithPeople(String[] people) { + Bundle extras = new Bundle(); + extras.putObject(Notification.EXTRA_PEOPLE_LIST, people); + return extras; + } + + private NotificationRecord getNotificationRecordWithPeople(String[] people) { + // set up notification record + NotificationRecord r = mock(NotificationRecord.class); + StatusBarNotification sbn = mock(StatusBarNotification.class); + Notification notification = mock(Notification.class); + notification.extras = makeExtrasBundleWithPeople(people); + when(sbn.getNotification()).thenReturn(notification); + when(r.getSbn()).thenReturn(sbn); + return r; + } + @Test public void testIsMessage() { NotificationRecord r = getNotificationRecord(); @@ -309,4 +333,111 @@ public class ZenModeFilteringTest extends UiServiceTestCase { assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r)); } + + @Test + public void testMatchesCallFilter_repeatCallers_directMatch() { + // after calls given an email with an exact string match, make sure that + // matchesCallFilter returns the right thing + String[] mailSource = new String[]{"mailto:hello.world"}; + mZenModeFiltering.recordCall(getNotificationRecordWithPeople(mailSource)); + + // set up policy to only allow repeat callers + Policy policy = new Policy( + PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE); + + // check whether matchesCallFilter returns the right thing + Bundle inputMatches = makeExtrasBundleWithPeople(new String[]{"mailto:hello.world"}); + Bundle inputWrong = makeExtrasBundleWithPeople(new String[]{"mailto:nope"}); + assertTrue(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + inputMatches, null, 0, 0)); + assertFalse(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + inputWrong, null, 0, 0)); + } + + @Test + public void testMatchesCallFilter_repeatCallers_telephoneVariants() { + // set up telephony manager behavior + when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); + + String[] telSource = new String[]{"tel:+1-617-555-1212"}; + mZenModeFiltering.recordCall(getNotificationRecordWithPeople(telSource)); + + // set up policy to only allow repeat callers + Policy policy = new Policy( + PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE); + + // cases to test: + // - identical number + // - same number, different formatting + // - different number + // - garbage + Bundle identical = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"}); + Bundle same = makeExtrasBundleWithPeople(new String[]{"tel:16175551212"}); + Bundle different = makeExtrasBundleWithPeople(new String[]{"tel:123-456-7890"}); + Bundle garbage = makeExtrasBundleWithPeople(new String[]{"asdfghjkl;"}); + + assertTrue("identical numbers should match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + identical, null, 0, 0)); + assertTrue("equivalent but non-identical numbers should match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + same, null, 0, 0)); + assertFalse("non-equivalent numbers should not match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + different, null, 0, 0)); + assertFalse("non-tel strings should not match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + garbage, null, 0, 0)); + } + + @Test + public void testMatchesCallFilter_repeatCallers_urlEncodedTels() { + // this is not intended to be a supported case but is one that we have seen + // sometimes in the wild, so make sure we handle url-encoded telephone numbers correctly + // when somebody provides one. + + // set up telephony manager behavior + when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us"); + + String[] telSource = new String[]{"tel:%2B16175551212"}; + mZenModeFiltering.recordCall(getNotificationRecordWithPeople(telSource)); + + // set up policy to only allow repeat callers + Policy policy = new Policy( + PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE); + + // test cases for various forms of the same phone number and different ones + Bundle same1 = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"}); + Bundle same2 = makeExtrasBundleWithPeople(new String[]{"tel:%2B1-617-555-1212"}); + Bundle same3 = makeExtrasBundleWithPeople(new String[]{"tel:6175551212"}); + Bundle different1 = makeExtrasBundleWithPeople(new String[]{"tel:%2B16175553434"}); + Bundle different2 = makeExtrasBundleWithPeople(new String[]{"tel:+16175553434"}); + + assertTrue("same number should match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + same1, null, 0, 0)); + assertTrue("same number should match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + same2, null, 0, 0)); + assertTrue("same number should match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + same3, null, 0, 0)); + assertFalse("different number should not match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + different1, null, 0, 0)); + assertFalse("different number should not match", + ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS, + policy, UserHandle.SYSTEM, + different2, null, 0, 0)); + } } 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 30ad1f93bdf3..3298d1184cdc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -85,7 +85,6 @@ import static com.android.server.wm.ActivityRecord.State.STARTED; import static com.android.server.wm.ActivityRecord.State.STOPPED; import static com.android.server.wm.ActivityRecord.State.STOPPING; import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS; -import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; @@ -3114,7 +3113,7 @@ public class ActivityRecordTests extends WindowTestsBase { } @Test - public void testInClosingAnimation_doNotHideSurface() { + public void testInClosingAnimation_visibilityNotCommitted_doNotHideSurface() { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); makeWindowVisibleAndDrawn(app); @@ -3123,16 +3122,45 @@ public class ActivityRecordTests extends WindowTestsBase { mDisplayContent.mClosingApps.add(app.mActivityRecord); mDisplayContent.prepareAppTransition(TRANSIT_CLOSE); - // Update visibility and call to remove window - app.mActivityRecord.commitVisibility(false, false); + // Remove window during transition, so it is requested to hide, but won't be committed until + // the transition is finished. + app.mActivityRecord.onRemovedFromDisplay(); + + assertTrue(mDisplayContent.mClosingApps.contains(app.mActivityRecord)); + assertFalse(app.mActivityRecord.isVisibleRequested()); + assertTrue(app.mActivityRecord.isVisible()); + assertTrue(app.mActivityRecord.isSurfaceShowing()); + + // Start transition. app.mActivityRecord.prepareSurfaces(); // Because the app is waiting for transition, it should not hide the surface. assertTrue(app.mActivityRecord.isSurfaceShowing()); + } + + @Test + public void testInClosingAnimation_visibilityCommitted_hideSurface() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + makeWindowVisibleAndDrawn(app); + + // Put the activity in close transition. + mDisplayContent.mOpeningApps.clear(); + mDisplayContent.mClosingApps.add(app.mActivityRecord); + mDisplayContent.prepareAppTransition(TRANSIT_CLOSE); + + // Commit visibility before start transition. + app.mActivityRecord.commitVisibility(false, false); + + assertFalse(app.mActivityRecord.isVisibleRequested()); + assertFalse(app.mActivityRecord.isVisible()); + assertTrue(app.mActivityRecord.isSurfaceShowing()); + + // Start transition. + app.mActivityRecord.prepareSurfaces(); - // Ensure onAnimationFinished will callback when the closing animation is finished. - verify(app.mActivityRecord).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), - eq(null)); + // Because the app visibility has been committed before the transition start, it should hide + // the surface. + assertFalse(app.mActivityRecord.isSurfaceShowing()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index 7c340ecac2c7..8ada97147dd3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -191,7 +191,8 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { public void onFixedRotationFinished(int displayId) {} @Override - public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {} + public void onKeepClearAreasChanged(int displayId, List<Rect> restricted, + List<Rect> unrestricted) {} }; int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener); for (int i = 0; i < displayIds.length; i++) { diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java index 24fda17dbe79..27e8d69aa762 100644 --- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java +++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java @@ -16,6 +16,8 @@ package com.android.server.usage; +import static android.app.ActivityManager.procStateToString; + import static com.android.server.usage.UsageStatsService.DEBUG_RESPONSE_STATS; import android.annotation.ElapsedRealtimeLong; @@ -23,6 +25,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager.ProcessState; import android.app.usage.BroadcastResponseStats; import android.os.UserHandle; import android.text.TextUtils; @@ -69,15 +72,26 @@ class BroadcastResponseStatsTracker { private SparseArray<SparseArray<UserBroadcastResponseStats>> mUserResponseStats = new SparseArray<>(); + private AppStandbyInternal mAppStandby; + + BroadcastResponseStatsTracker(@NonNull AppStandbyInternal appStandby) { + mAppStandby = appStandby; + } + // TODO (206518114): Move all callbacks handling to a handler thread. void reportBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage, UserHandle targetUser, long idForResponseEvent, - @ElapsedRealtimeLong long timestampMs) { + @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState) { if (DEBUG_RESPONSE_STATS) { - Slog.d(TAG, TextUtils.formatSimple( - "reportBroadcastDispatchEvent; srcUid=%d, tgtPkg=%s, tgtUsr=%d, id=%d, ts=%s", + Slog.d(TAG, TextUtils.formatSimple("reportBroadcastDispatchEvent; " + + "srcUid=%d, tgtPkg=%s, tgtUsr=%d, id=%d, ts=%s, state=%s", sourceUid, targetPackage, targetUser, idForResponseEvent, - TimeUtils.formatDuration(timestampMs))); + TimeUtils.formatDuration(timestampMs), procStateToString(targetUidProcState))); + } + if (targetUidProcState <= mAppStandby.getBroadcastResponseFgThresholdState()) { + // No need to track the broadcast response state while the target app is + // in the foreground. + return; } synchronized (mLock) { final LongSparseArray<BroadcastEvent> broadcastEvents = @@ -132,8 +146,7 @@ class BroadcastResponseStatsTracker { if (dispatchTimestampMs >= timestampMs) { continue; } - // TODO (206518114): Make the constant configurable. - if (elapsedDurationMs <= 2 * 60 * 1000) { + if (elapsedDurationMs <= mAppStandby.getBroadcastResponseWindowDurationMs()) { final BroadcastEvent broadcastEvent = broadcastEvents.valueAt(i); final BroadcastResponseStats responseStats = getBroadcastResponseStats(broadcastEvent); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index e90d28a11e1d..98a41bcf5adf 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -42,6 +42,7 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.ActivityManager.ProcessState; import android.app.AppOpsManager; import android.app.IUidObserver; import android.app.PendingIntent; @@ -281,7 +282,7 @@ public class UsageStatsService extends SystemService implements mHandler = new H(BackgroundThread.get().getLooper()); mAppStandby = mInjector.getAppStandbyController(getContext()); - mResponseStatsTracker = new BroadcastResponseStatsTracker(); + mResponseStatsTracker = new BroadcastResponseStatsTracker(mAppStandby); mAppTimeLimit = new AppTimeLimitController(getContext(), new AppTimeLimitController.TimeLimitCallbackListener() { @@ -3042,9 +3043,9 @@ public class UsageStatsService extends SystemService implements @Override public void reportBroadcastDispatched(int sourceUid, @NonNull String targetPackage, @NonNull UserHandle targetUser, long idForResponseEvent, - @ElapsedRealtimeLong long timestampMs) { + @ElapsedRealtimeLong long timestampMs, @ProcessState int targetUidProcState) { mResponseStatsTracker.reportBroadcastDispatchEvent(sourceUid, targetPackage, - targetUser, idForResponseEvent, timestampMs); + targetUser, idForResponseEvent, timestampMs, targetUidProcState); } @Override diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index c5fc4365df7a..27d423b3bc1e 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -18,6 +18,7 @@ package android.telecom; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -3156,15 +3157,27 @@ public abstract class ConnectionService extends Service { } /** - * Create a {@code Connection} for a new unknown call. An unknown call is a call originating - * from the ConnectionService that was neither a user-initiated outgoing call, nor an incoming - * call created using - * {@code TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}. + * Calls of this type are created using + * {@link TelecomManager#addNewUnknownCall(PhoneAccountHandle, Bundle)}. Unknown calls + * are used for representing calls which become known to the {@link ConnectionService} + * midway through the call. + * + * For example, a call transferred from one device to answer would surface as an active + * call in Telecom instead of going through a typical Ringing to Active transition, or + * Dialing to Active transition. + * + * A {@link ConnectionService} can return {@code null} (the default behavior) + * if it is not able to handle a request for the requested unknown connection. + * + * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, android.os.Bundle)}. * * @hide */ - public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, - ConnectionRequest request) { + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public @Nullable Connection onCreateUnknownConnection( + @NonNull PhoneAccountHandle connectionManagerPhoneAccount, + @NonNull ConnectionRequest request) { return null; } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index a74930b0da12..3c277b7de018 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4540,9 +4540,7 @@ public class CarrierConfigManager { * Passing this value as {@link #KEY_SUBSCRIPTION_GROUP_UUID_STRING} will remove the * subscription from a group instead of adding it to a group. * - * TODO: Expose in a future release. - * - * @hide + * <p>This value will work all the way back to {@link android.os.Build.VERSION_CODES#Q}. */ public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000"; @@ -4555,9 +4553,7 @@ public class CarrierConfigManager { * <p>If set to {@link #REMOVE_GROUP_UUID_STRING}, then the subscription will be removed from * its current group. * - * TODO: unhide this key. - * - * @hide + * <p>This key will work all the way back to {@link android.os.Build.VERSION_CODES#Q}. */ public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING = "subscription_group_uuid_string"; @@ -4605,17 +4601,15 @@ public class CarrierConfigManager { "data_switch_validation_min_gap_long"; /** - * A boolean property indicating whether this subscription should be managed as an opportunistic - * subscription. - * - * If true, then this subscription will be selected based on available coverage and will not be - * available for a user in settings menus for selecting macro network providers. If unset, - * defaults to “false”. - * - * TODO: unhide this key. - * - * @hide - */ + * A boolean property indicating whether this subscription should be managed as an opportunistic + * subscription. + * + * If true, then this subscription will be selected based on available coverage and will not be + * available for a user in settings menus for selecting macro network providers. If unset, + * defaults to “false”. + * + * <p>This key will work all the way back to {@link android.os.Build.VERSION_CODES#Q}. + */ public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL = "is_opportunistic_subscription_bool"; @@ -8920,7 +8914,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true); sDefaults.putBoolean(KEY_HIDE_ENABLE_2G, false); sDefaults.putStringArray(KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY, - new String[]{"ia", "default", "mms", "dun"}); + new String[]{"ia", "default"}); sDefaults.putBoolean(KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL, false); sDefaults.putBoolean(KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL, false); sDefaults.putBoolean(KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL, true); diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp index 4e98f4264e11..d4fa1dda8bdf 100644 --- a/tests/ApkVerityTest/Android.bp +++ b/tests/ApkVerityTest/Android.bp @@ -24,14 +24,21 @@ package { java_test_host { name: "ApkVerityTest", srcs: ["src/**/*.java"], - libs: ["tradefed", "compatibility-tradefed", "compatibility-host-util"], + libs: [ + "tradefed", + "compatibility-tradefed", + "compatibility-host-util", + ], static_libs: [ "block_device_writer_jar", "frameworks-base-hostutils", ], - test_suites: ["general-tests", "vts"], - target_required: [ - "block_device_writer_module", + test_suites: [ + "general-tests", + "vts", + ], + data_device_bins: [ + "block_device_writer", ], data: [ ":ApkVerityTestCertDer", diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp index 0b5f0f611916..e5d009dc10fd 100644 --- a/tests/ApkVerityTest/block_device_writer/Android.bp +++ b/tests/ApkVerityTest/block_device_writer/Android.bp @@ -24,12 +24,7 @@ package { } cc_test { - // Depending on how the test runs, the executable may be uploaded to different location. - // Before the bug in the file pusher is fixed, workaround by making the name unique. - // See b/124718249#comment12. - name: "block_device_writer_module", - stem: "block_device_writer", - + name: "block_device_writer", srcs: ["block_device_writer.cpp"], cflags: [ "-D_FILE_OFFSET_BITS=64", @@ -38,31 +33,25 @@ cc_test { "-Wextra", "-g", ], - shared_libs: ["libbase", "libutils"], - // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when - // the uploader does not pick up the executable from correct output location. The following - // workaround allows the test to: - // * upload the 32-bit exectuable for both 32 and 64 bits devices to use - // * refer to the same executable name in Java - // * no need to force the Java test to be archiecture specific. - // - // See b/145573317 for details. - multilib: { - lib32: { - suffix: "", - }, - lib64: { - suffix: "64", // not really used - }, - }, + shared_libs: [ + "libbase", + "libutils", + ], + compile_multilib: "first", auto_gen_config: false, - test_suites: ["general-tests", "pts", "vts"], + test_suites: [ + "general-tests", + "vts", + ], gtest: false, } java_library_host { name: "block_device_writer_jar", srcs: ["src/**/*.java"], - libs: ["tradefed", "junit"], + libs: [ + "tradefed", + "junit", + ], } diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java index 5c2c15b22bb0..730daf32f20d 100644 --- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java +++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java @@ -32,7 +32,7 @@ import java.util.ArrayList; * <p>To use this class, please push block_device_writer binary to /data/local/tmp. * 1. In Android.bp, add: * <pre> - * target_required: ["block_device_writer_module"], + * data_device_bins: ["block_device_writer"], * </pre> * 2. In AndroidText.xml, add: * <pre> diff --git a/tools/lint/README.md b/tools/lint/README.md index 2b6d65b318e5..b534b62cb395 100644 --- a/tools/lint/README.md +++ b/tools/lint/README.md @@ -40,6 +40,9 @@ m out/soong/.intermediates/frameworks/base/services/autofill/services.autofill/a - If you want to build lint reports for more than 1 module and they include a common module in their `defaults` field, e.g. `platform_service_defaults`, you can add the `lint` property to that common module instead of adding it in every module. +- If you want to run a single lint type, use the `ANDROID_LINT_CHECK` + environment variable with the id of the lint. For example: + `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html` ## Create or update a baseline |