diff options
638 files changed, 21450 insertions, 17638 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..ddcc74696d94 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -216,4 +216,11 @@ 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(); } 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..050e3df5b5e1 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,14 @@ 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; + /** * Whether we should allow apps into the * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket or not. @@ -1774,6 +1782,10 @@ public class AppStandbyController } } + @Override + public long getBroadcastResponseWindowDurationMs() { + return mBroadcastResponseWindowDurationMillis; + } @Override public void flushToDisk() { @@ -2042,6 +2054,10 @@ public class AppStandbyController TimeUtils.formatDuration(mSystemUpdateUsageTimeoutMillis, pw); pw.println(); + pw.print(" mBroadcastResponseWindowDurationMillis="); + TimeUtils.formatDuration(mBroadcastResponseWindowDurationMillis, pw); + pw.println(); + pw.println(); pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled); pw.print(" mAllowRestrictedBucket="); @@ -2473,6 +2489,8 @@ 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"; 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 +2520,8 @@ 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; ConstantsObserver(Handler handler) { super(handler); @@ -2619,6 +2639,11 @@ 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; 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/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..2714809e0c3d 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", 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 c4df0b326592..dd0614648f8c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4183,6 +4183,7 @@ package android.app { method public void openContextMenu(android.view.View); method public void openOptionsMenu(); method public void overridePendingTransition(int, int); + method public void overridePendingTransition(int, int, int); method public void postponeEnterTransition(); method public void recreate(); method public void registerActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks); @@ -4486,6 +4487,7 @@ package android.app { method public static android.app.ActivityOptions makeBasic(); method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int); + method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int); method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.view.View, String); method @java.lang.SafeVarargs public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair<android.view.View,java.lang.String>...); @@ -12223,7 +12225,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 } @@ -12951,6 +12953,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(); @@ -13982,6 +13988,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; } @@ -22721,6 +22728,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(); @@ -22754,6 +22762,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); @@ -23280,8 +23289,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; } @@ -23290,7 +23303,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(@Nullable 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); } @@ -31681,6 +31697,7 @@ package android.os { method public boolean isDeviceLightIdleMode(); method public boolean isIgnoringBatteryOptimizations(String); method public boolean isInteractive(); + method public boolean isLowPowerStandbyEnabled(); method public boolean isPowerSaveMode(); method public boolean isRebootingUserspaceSupported(); method @Deprecated public boolean isScreenOn(); @@ -31692,6 +31709,7 @@ package android.os { field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000 field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED"; field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED"; + field public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED = "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED"; field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED"; field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2 @@ -32416,6 +32434,7 @@ package android.os.storage { method @Nullable public android.os.storage.StorageVolume getStorageVolume(@NonNull java.io.File); method @NonNull public android.os.storage.StorageVolume getStorageVolume(@NonNull android.net.Uri); method @NonNull public java.util.List<android.os.storage.StorageVolume> getStorageVolumes(); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public java.util.List<android.os.storage.StorageVolume> getStorageVolumesIncludingSharedProfiles(); method @NonNull public java.util.UUID getUuidForPath(@NonNull java.io.File) throws java.io.IOException; method public boolean isAllocationSupported(@NonNull java.io.FileDescriptor); method public boolean isCacheBehaviorGroup(java.io.File) throws java.io.IOException; @@ -32449,6 +32468,7 @@ package android.os.storage { method public String getDescription(android.content.Context); method @Nullable public java.io.File getDirectory(); method @Nullable public String getMediaStoreVolumeName(); + method @NonNull public android.os.UserHandle getOwner(); method public String getState(); method @Nullable public java.util.UUID getStorageUuid(); method @Nullable public String getUuid(); @@ -37876,6 +37896,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"; } @@ -37926,21 +37947,23 @@ package android.service.autofill { } public static final class Dataset.Builder { - ctor public Dataset.Builder(@NonNull android.widget.RemoteViews); + ctor @Deprecated public Dataset.Builder(@NonNull android.widget.RemoteViews); + ctor public Dataset.Builder(@NonNull android.service.autofill.Presentations); ctor public Dataset.Builder(); method @NonNull public android.service.autofill.Dataset build(); method @NonNull public android.service.autofill.Dataset.Builder setAuthentication(@Nullable android.content.IntentSender); + method @NonNull public android.service.autofill.Dataset.Builder setField(@NonNull android.view.autofill.AutofillId, @Nullable android.service.autofill.Field); method @NonNull public android.service.autofill.Dataset.Builder setId(@Nullable String); - method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation); - method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); - method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue); - method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews); - method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern); - method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews); - method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation); - method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); - method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation); - method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation); } public final class DateTransformation implements android.os.Parcelable android.service.autofill.Transformation { @@ -37957,6 +37980,19 @@ package android.service.autofill { field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.DateValueSanitizer> CREATOR; } + public final class Field { + method @Nullable public android.service.autofill.Presentations getPresentations(); + method @Nullable public android.view.autofill.AutofillValue getValue(); + } + + public static final class Field.Builder { + ctor public Field.Builder(); + method @NonNull public android.service.autofill.Field build(); + method @NonNull public android.service.autofill.Field.Builder setFilter(@Nullable java.util.regex.Pattern); + method @NonNull public android.service.autofill.Field.Builder setPresentations(@NonNull android.service.autofill.Presentations); + method @NonNull public android.service.autofill.Field.Builder setValue(@NonNull android.view.autofill.AutofillValue); + } + public final class FieldClassification { method @NonNull public java.util.List<android.service.autofill.FieldClassification.Match> getMatches(); } @@ -38016,6 +38052,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(); @@ -38030,6 +38067,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 } @@ -38039,11 +38077,14 @@ package android.service.autofill { method @NonNull public android.service.autofill.FillResponse.Builder addDataset(@Nullable android.service.autofill.Dataset); method @NonNull public android.service.autofill.FillResponse build(); method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long); - method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews); - method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation); - method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation); + method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews); + method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation); + method @Deprecated @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews, @Nullable android.service.autofill.InlinePresentation, @Nullable android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.service.autofill.Presentations); method @NonNull public android.service.autofill.FillResponse.Builder setClientState(@Nullable android.os.Bundle); + method @NonNull public android.service.autofill.FillResponse.Builder setDialogHeader(@NonNull android.widget.RemoteViews); method @NonNull public android.service.autofill.FillResponse.Builder setFieldClassificationIds(@NonNull android.view.autofill.AutofillId...); + method @NonNull public android.service.autofill.FillResponse.Builder setFillDialogTriggerIds(@NonNull android.view.autofill.AutofillId...); method @NonNull public android.service.autofill.FillResponse.Builder setFlags(int); method @NonNull public android.service.autofill.FillResponse.Builder setFooter(@NonNull android.widget.RemoteViews); method @NonNull public android.service.autofill.FillResponse.Builder setHeader(@NonNull android.widget.RemoteViews); @@ -38088,6 +38129,22 @@ package android.service.autofill { public interface OnClickAction { } + public final class Presentations { + method @Nullable public android.widget.RemoteViews getDialogPresentation(); + method @Nullable public android.service.autofill.InlinePresentation getInlinePresentation(); + method @Nullable public android.service.autofill.InlinePresentation getInlineTooltipPresentation(); + method @Nullable public android.widget.RemoteViews getMenuPresentation(); + } + + public static final class Presentations.Builder { + ctor public Presentations.Builder(); + method @NonNull public android.service.autofill.Presentations build(); + method @NonNull public android.service.autofill.Presentations.Builder setDialogPresentation(@NonNull android.widget.RemoteViews); + method @NonNull public android.service.autofill.Presentations.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Presentations.Builder setInlineTooltipPresentation(@NonNull android.service.autofill.InlinePresentation); + method @NonNull public android.service.autofill.Presentations.Builder setMenuPresentation(@NonNull android.widget.RemoteViews); + } + public final class RegexValidator implements android.os.Parcelable android.service.autofill.Validator { ctor public RegexValidator(@NonNull android.view.autofill.AutofillId, @NonNull java.util.regex.Pattern); method public int describeContents(); @@ -49166,6 +49223,8 @@ package android.view { public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable { ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage); method public int describeContents(); + method public void notifyConfigurationChanged(@NonNull android.content.res.Configuration); + method public void notifyDetachedFromWindow(); method public void release(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControlViewHost.SurfacePackage> CREATOR; diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 1f40e5904ccd..df61a968ffc0 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -2,6 +2,7 @@ package android { public static final class Manifest.permission { + field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS"; field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT"; } @@ -162,6 +163,8 @@ package android.location { public class LocationManager { method @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public boolean injectLocation(@NonNull android.location.Location); + method @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) public boolean isAutomotiveGnssSuspended(); + method @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) public void setAutomotiveGnssSuspended(boolean); } } @@ -443,6 +446,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 { @@ -538,10 +552,6 @@ package android.os.storage { field public static final int APP_IO_BLOCKED_REASON_UNKNOWN = 0; // 0x0 } - public final class StorageVolume implements android.os.Parcelable { - method @NonNull public android.os.UserHandle getOwner(); - } - } package android.provider { @@ -556,6 +566,26 @@ package android.provider { field public static final String NAMESPACE_DEVICE_IDLE = "device_idle"; } + public static final class Settings.Global extends android.provider.Settings.NameValueTable { + field public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled"; + field public static final String BLE_SCAN_BACKGROUND_MODE = "ble_scan_background_mode"; + field public static final String BLE_SCAN_BALANCED_INTERVAL_MS = "ble_scan_balanced_interval_ms"; + field public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms"; + field public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS = "ble_scan_low_latency_interval_ms"; + field public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS = "ble_scan_low_latency_window_ms"; + field public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS = "ble_scan_low_power_interval_ms"; + field public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms"; + field public static final String BLUETOOTH_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode"; + field public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device"; + field public static final String BLUETOOTH_DISABLED_PROFILES = "bluetooth_disabled_profiles"; + } + + public static final class Settings.Secure extends android.provider.Settings.NameValueTable { + field public static final String BLUETOOTH_ADDRESS = "bluetooth_address"; + field public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid"; + field public static final String BLUETOOTH_NAME = "bluetooth_name"; + } + } package android.telephony { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index d8021d08de4f..ec39e9f4df2d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -37,7 +37,6 @@ package android { field public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; field public static final String ASSOCIATE_COMPANION_DEVICES = "android.permission.ASSOCIATE_COMPANION_DEVICES"; - field public static final String AUTOMOTIVE_GNSS_CONTROLS = "android.permission.AUTOMOTIVE_GNSS_CONTROLS"; field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BACKUP = "android.permission.BACKUP"; field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION"; @@ -73,6 +72,7 @@ package android { field public static final String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE"; field public static final String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE"; field public static final String BIND_TIME_ZONE_PROVIDER_SERVICE = "android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"; + field public static final String BIND_TRACE_REPORT_SERVICE = "android.permission.BIND_TRACE_REPORT_SERVICE"; field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE"; field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT"; field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE"; @@ -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"; @@ -169,6 +170,7 @@ package android { field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE"; field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION"; field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; + field public static final String MANAGE_LOW_POWER_STANDBY = "android.permission.MANAGE_LOW_POWER_STANDBY"; field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION"; field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS"; field public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS = "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS"; @@ -302,6 +304,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"; @@ -2113,6 +2116,7 @@ package android.app.smartspace { method @Nullable public android.net.Uri getSliceUri(); method @NonNull public String getSmartspaceTargetId(); method @Nullable public String getSourceNotificationKey(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData getTemplateData(); method @NonNull public android.os.UserHandle getUserHandle(); method @Nullable public android.appwidget.AppWidgetProviderInfo getWidget(); method public boolean isSensitive(); @@ -2143,6 +2147,14 @@ package android.app.smartspace { field public static final int FEATURE_UPCOMING_ALARM = 23; // 0x17 field public static final int FEATURE_WEATHER = 1; // 0x1 field public static final int FEATURE_WEATHER_ALERT = 10; // 0xa + field public static final int UI_TEMPLATE_CAROUSEL = 4; // 0x4 + field public static final int UI_TEMPLATE_COMBINED_CARDS = 6; // 0x6 + field public static final int UI_TEMPLATE_DEFAULT = 1; // 0x1 + field public static final int UI_TEMPLATE_HEAD_TO_HEAD = 5; // 0x5 + field public static final int UI_TEMPLATE_SUB_CARD = 7; // 0x7 + field public static final int UI_TEMPLATE_SUB_IMAGE = 2; // 0x2 + field public static final int UI_TEMPLATE_SUB_LIST = 3; // 0x3 + field public static final int UI_TEMPLATE_UNDEFINED = 0; // 0x0 } public static final class SmartspaceTarget.Builder { @@ -2161,6 +2173,7 @@ package android.app.smartspace { method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setShouldShowExpanded(boolean); method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSliceUri(@NonNull android.net.Uri); method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setSourceNotificationKey(@NonNull String); + method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setTemplateData(@Nullable android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData); method @NonNull public android.app.smartspace.SmartspaceTarget.Builder setWidget(@NonNull android.appwidget.AppWidgetProviderInfo); } @@ -2189,6 +2202,177 @@ package android.app.smartspace { } +package android.app.smartspace.uitemplatedata { + + public final class SmartspaceCarouselUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData { + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getCarouselAction(); + method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem> getCarouselItems(); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData> CREATOR; + } + + public static final class SmartspaceCarouselUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder { + ctor public SmartspaceCarouselUiTemplateData.Builder(@NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem>); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData build(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction); + } + + public static final class SmartspaceCarouselUiTemplateData.CarouselItem implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getImage(); + method @Nullable public CharSequence getLowerText(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getTapAction(); + method @Nullable public CharSequence getUpperText(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem> CREATOR; + } + + public static final class SmartspaceCarouselUiTemplateData.CarouselItem.Builder { + ctor public SmartspaceCarouselUiTemplateData.CarouselItem.Builder(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem build(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setImage(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setLowerText(@Nullable CharSequence); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setTapAction(@Nullable android.app.smartspace.uitemplatedata.SmartspaceTapAction); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCarouselUiTemplateData.CarouselItem.Builder setUpperText(@Nullable CharSequence); + } + + public final class SmartspaceCombinedCardsUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData { + method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData> getCombinedCardDataList(); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceCombinedCardsUiTemplateData> CREATOR; + } + + public static final class SmartspaceCombinedCardsUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder { + ctor public SmartspaceCombinedCardsUiTemplateData.Builder(@NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData>); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceCombinedCardsUiTemplateData build(); + } + + public class SmartspaceDefaultUiTemplateData implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getPrimaryTapAction(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubTitleIcon(); + method @Nullable public CharSequence getSubtitleText(); + method @Nullable public CharSequence getSupplementalAlarmText(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSupplementalSubtitleIcon(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSupplementalSubtitleTapAction(); + method @Nullable public CharSequence getSupplementalSubtitleText(); + method public int getTemplateType(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getTitleIcon(); + method @Nullable public CharSequence getTitleText(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData> CREATOR; + } + + public static class SmartspaceDefaultUiTemplateData.Builder { + ctor public SmartspaceDefaultUiTemplateData.Builder(int); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData build(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setPrimaryTapAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSubTitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSubtitleText(@NonNull CharSequence); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalAlarmText(@NonNull CharSequence); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleTapAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setSupplementalSubtitleText(@NonNull CharSequence); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setTitleIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder setTitleText(@NonNull CharSequence); + } + + public final class SmartspaceHeadToHeadUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData { + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getHeadToHeadAction(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getHeadToHeadFirstCompetitorIcon(); + method @Nullable public CharSequence getHeadToHeadFirstCompetitorText(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getHeadToHeadSecondCompetitorIcon(); + method @Nullable public CharSequence getHeadToHeadSecondCompetitorText(); + method @Nullable public CharSequence getHeadToHeadTitle(); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData> CREATOR; + } + + public static final class SmartspaceHeadToHeadUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder { + ctor public SmartspaceHeadToHeadUiTemplateData.Builder(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData build(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadAction(@Nullable android.app.smartspace.uitemplatedata.SmartspaceTapAction); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadFirstCompetitorIcon(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadFirstCompetitorText(@Nullable CharSequence); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadSecondCompetitorIcon(@Nullable android.app.smartspace.uitemplatedata.SmartspaceIcon); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadSecondCompetitorText(@Nullable CharSequence); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceHeadToHeadUiTemplateData.Builder setHeadToHeadTitle(@Nullable CharSequence); + } + + public final class SmartspaceIcon implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public CharSequence getContentDescription(); + method @NonNull public android.graphics.drawable.Icon getIcon(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceIcon> CREATOR; + } + + public static final class SmartspaceIcon.Builder { + ctor public SmartspaceIcon.Builder(@NonNull android.graphics.drawable.Icon); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon build(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon.Builder setContentDescription(@NonNull CharSequence); + } + + public final class SmartspaceSubCardUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData { + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubCardAction(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubCardIcon(); + method @Nullable public CharSequence getSubCardText(); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData> CREATOR; + } + + public static final class SmartspaceSubCardUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder { + ctor public SmartspaceSubCardUiTemplateData.Builder(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData build(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData.Builder setSubCardAction(@NonNull CharSequence); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubCardUiTemplateData.Builder setSubCardAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction); + } + + public final class SmartspaceSubImageUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData { + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubImageAction(); + method @NonNull public java.util.List<java.lang.CharSequence> getSubImageTexts(); + method @NonNull public java.util.List<android.app.smartspace.uitemplatedata.SmartspaceIcon> getSubImages(); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData> CREATOR; + } + + public static final class SmartspaceSubImageUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder { + ctor public SmartspaceSubImageUiTemplateData.Builder(@NonNull java.util.List<java.lang.CharSequence>, @NonNull java.util.List<android.app.smartspace.uitemplatedata.SmartspaceIcon>); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData build(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubImageUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction); + } + + public final class SmartspaceSubListUiTemplateData extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData { + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceTapAction getSubListAction(); + method @Nullable public android.app.smartspace.uitemplatedata.SmartspaceIcon getSubListIcon(); + method @NonNull public java.util.List<java.lang.CharSequence> getSubListTexts(); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData> CREATOR; + } + + public static final class SmartspaceSubListUiTemplateData.Builder extends android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData.Builder { + ctor public SmartspaceSubListUiTemplateData.Builder(@NonNull java.util.List<java.lang.CharSequence>); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData build(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData.Builder setCarouselAction(@NonNull android.app.smartspace.uitemplatedata.SmartspaceTapAction); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceSubListUiTemplateData.Builder setSubListIcon(@NonNull android.app.smartspace.uitemplatedata.SmartspaceIcon); + } + + public final class SmartspaceTapAction implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.os.Bundle getExtras(); + method @Nullable public CharSequence getId(); + method @Nullable public android.content.Intent getIntent(); + method @Nullable public android.app.PendingIntent getPendingIntent(); + method @Nullable public android.os.UserHandle getUserHandle(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.smartspace.uitemplatedata.SmartspaceTapAction> CREATOR; + } + + public static final class SmartspaceTapAction.Builder { + ctor public SmartspaceTapAction.Builder(@NonNull CharSequence); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction build(); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setIntent(@NonNull android.content.Intent); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setPendingIntent(@NonNull android.app.PendingIntent); + method @NonNull public android.app.smartspace.uitemplatedata.SmartspaceTapAction.Builder setUserHandle(@Nullable android.os.UserHandle); + } + +} + package android.app.time { public final class Capabilities { @@ -2358,12 +2542,21 @@ package android.apphibernation { public class AppHibernationManager { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.List<java.lang.String> getHibernatingPackagesForUser(); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.Map<java.lang.String,android.apphibernation.HibernationStats> getHibernationStatsForUser(@NonNull java.util.Set<java.lang.String>); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.Map<java.lang.String,android.apphibernation.HibernationStats> getHibernationStatsForUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingForUser(@NonNull String); method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingGlobally(@NonNull String); method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingForUser(@NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void setHibernatingGlobally(@NonNull String, boolean); } + public final class HibernationStats implements android.os.Parcelable { + method public int describeContents(); + method public long getDiskBytesSaved(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.apphibernation.HibernationStats> CREATOR; + } + } package android.companion { @@ -2420,6 +2613,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); @@ -2431,6 +2626,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>); } @@ -2498,7 +2695,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"; @@ -5344,9 +5541,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 { @@ -5364,7 +5561,6 @@ package android.location { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.location.Location getLastKnownLocation(@NonNull String, @NonNull android.location.LastLocationRequest); method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void injectGnssMeasurementCorrections(@NonNull android.location.GnssMeasurementCorrections); method public boolean isAdasGnssLocationEnabled(); - method @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS) public boolean isAutoGnssSuspended(); method public boolean isExtraLocationControllerPackageEnabled(); method public boolean isLocationEnabledForUser(@NonNull android.os.UserHandle); method public boolean isProviderEnabledForUser(@NonNull String, @NonNull android.os.UserHandle); @@ -5376,8 +5572,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(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS) public void setAutoGnssSuspended(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); @@ -5410,7 +5605,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); @@ -5426,9 +5621,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); } @@ -5680,6 +5875,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 @@ -5688,7 +5884,10 @@ 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 @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 } @@ -6288,6 +6487,7 @@ package android.media.tv { method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig); method @NonNull public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String); + method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String); method public int getClientPriority(int, @Nullable String); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos(); method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList(); @@ -6460,7 +6660,7 @@ package android.media.tv.tuner { method @Nullable public android.media.tv.tuner.DemuxCapabilities getDemuxCapabilities(); method @Nullable public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo(); method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]); - method @Nullable public android.media.tv.tuner.frontend.FrontendStatusReadiness[] getFrontendStatusReadiness(@NonNull int[]); + method @NonNull public java.util.List<android.media.tv.tuner.frontend.FrontendStatusReadiness> getFrontendStatusReadiness(@NonNull int[]); method @IntRange(from=0xffffffff) public int getMaxNumberOfFrontends(int); method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean hasUnusedFrontend(int); method public boolean isLowestPriority(int); @@ -7705,7 +7905,7 @@ package android.media.tv.tuner.frontend { method public boolean isLocked(); } - public class FrontendStatusReadiness { + public final class FrontendStatusReadiness { method public int getStatusReadiness(); method public int getStatusType(); field public static final int FRONTEND_STATUS_READINESS_STABLE = 3; // 0x3 @@ -8729,6 +8929,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); @@ -9126,11 +9328,14 @@ package android.os { method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplayAvailable(); method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressed(); method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isAmbientDisplaySuppressedForToken(@NonNull String); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public boolean isLowPowerStandbySupported(); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSaveEnabled(boolean); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setAdaptivePowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig); method @RequiresPermission(anyOf={android.Manifest.permission.BATTERY_PREDICTION, android.Manifest.permission.DEVICE_POWER}) public void setBatteryDischargePrediction(@NonNull java.time.Duration, boolean); method @RequiresPermission(android.Manifest.permission.POWER_SAVER) public boolean setDynamicPowerSaveHint(boolean, int); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setFullPowerSavePolicy(@NonNull android.os.BatterySaverPolicyConfig); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyActiveDuringMaintenance(boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void setLowPowerStandbyEnabled(boolean); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.POWER_SAVER}) public boolean setPowerSaveModeEnabled(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void suppressAmbientDisplay(@NonNull String, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.DEVICE_POWER, android.Manifest.permission.USER_ACTIVITY}) public void userActivity(long, int, int); @@ -10451,9 +10656,9 @@ package android.service.autofill { } public static final class Dataset.Builder { - ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation); + ctor @Deprecated public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation); method @NonNull public android.service.autofill.Dataset.Builder setContent(@NonNull android.view.autofill.AutofillId, @Nullable android.content.ClipData); - method @NonNull public android.service.autofill.Dataset.Builder setFieldInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation); + method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setFieldInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation); } public abstract class InlineSuggestionRenderService extends android.app.Service { @@ -11005,7 +11210,7 @@ package android.service.persistentdata { method @android.service.persistentdata.PersistentDataBlockManager.FlashLockState @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState(); method public long getMaximumDataBlockSize(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled(); - method @NonNull public String getPersistentDataPackageName(); + method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName(); method public byte[] read(); method @Deprecated @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void setOemUnlockEnabled(boolean); method @RequiresPermission("android.permission.OEM_UNLOCK_STATE") public void wipe(); @@ -11243,6 +11448,22 @@ package android.service.timezone { } +package android.service.tracing { + + public class TraceReportService extends android.app.Service { + ctor public TraceReportService(); + method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); + method public boolean onMessage(@NonNull android.os.Message); + method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams); + } + + public static final class TraceReportService.TraceParams { + method @NonNull public android.os.ParcelFileDescriptor getFd(); + method @NonNull public java.util.UUID getUuid(); + } + +} + package android.service.translation { public abstract class TranslationService extends android.app.Service { @@ -11635,6 +11856,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 0bdbd1037b7e..15148a93cbe7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2,6 +2,7 @@ package android { public static final class Manifest.permission { + field public static final String ACCESS_KEYGUARD_SECURE_STORAGE = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"; field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; @@ -105,6 +106,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); } @@ -152,7 +154,7 @@ package android.app { public class ActivityOptions { method @NonNull public static android.app.ActivityOptions fromBundle(@NonNull android.os.Bundle); - method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener); + method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener); method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener); method public static void setExitTransitionTimeout(long); method public void setLaunchActivityType(int); @@ -286,8 +288,8 @@ package android.app { } public class KeyguardManager { - method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean checkLock(int, @Nullable byte[]); - method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean setLock(int, @Nullable byte[], int, @Nullable byte[]); + method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE}) public boolean checkLock(int, @Nullable byte[]); + method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE}) public boolean setLock(int, @Nullable byte[], int, @Nullable byte[]); } public class LocaleManager { @@ -461,6 +463,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 +479,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"; @@ -581,6 +585,15 @@ package android.app.prediction { } +package android.app.trust { + + public class TrustManager { + method @RequiresPermission(android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) public void enableTrustAgentForUserForTest(@NonNull android.content.ComponentName, int); + method @RequiresPermission(android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) public void reportUserRequestedUnlock(int); + } + +} + package android.app.usage { public class NetworkStatsManager { @@ -1091,7 +1104,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); @@ -1718,7 +1731,9 @@ package android.os { } public final class PowerManager { + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, android.Manifest.permission.DEVICE_POWER}) public void forceLowPowerStandbyActive(boolean); field public static final String ACTION_ENHANCED_DISCHARGE_PREDICTION_CHANGED = "android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED"; + field @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public static final int SYSTEM_WAKELOCK = -2147483648; // 0x80000000 } public class Process { @@ -2359,6 +2374,14 @@ package android.service.quicksettings { } +package android.service.trust { + + public class TrustAgentService extends android.app.Service { + method public void onUserRequestedUnlock(); + } + +} + package android.service.voice { public class AlwaysOnHotwordDetector implements android.service.voice.HotwordDetector { diff --git a/core/java/Android.bp b/core/java/Android.bp index a2259f3ef7e2..8234f03100ca 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -38,6 +38,11 @@ filegroup { srcs: ["android/tracing/ITracingServiceProxy.aidl"], } +filegroup { + name: "TraceReportParams.aidl", + srcs: ["android/tracing/TraceReportParams.aidl"], +} + // These are subset of framework-core-sources that are needed by the // android.test.mock library. The implementation of android.test.mock references // private members of various components to allow mocking of classes that cannot diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 93d1e0393383..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; @@ -6131,8 +6145,31 @@ public class Activity extends ContextThemeWrapper * the outgoing activity. Use 0 for no animation. */ public void overridePendingTransition(int enterAnim, int exitAnim) { - ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(), - enterAnim, exitAnim); + overridePendingTransition(enterAnim, exitAnim, 0); + } + + /** + * Call immediately after one of the flavors of {@link #startActivity(Intent)} + * or {@link #finish} to specify an explicit transition animation to + * perform next. + * + * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN} an alternative + * to using this with starting activities is to supply the desired animation + * information through a {@link ActivityOptions} bundle to + * {@link #startActivity(Intent, Bundle)} or a related function. This allows + * you to specify a custom animation even when starting an activity from + * outside the context of the current top activity. + * + * @param enterAnim A resource ID of the animation resource to use for + * the incoming activity. Use 0 for no animation. + * @param exitAnim A resource ID of the animation resource to use for + * the outgoing activity. Use 0 for no animation. + * @param backgroundColor The background color to use for the background during the animation if + * the animation requires a background. Set to 0 to not override the default color. + */ + public void overridePendingTransition(int enterAnim, int exitAnim, int backgroundColor) { + ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(), enterAnim, + exitAnim, backgroundColor); } /** @@ -7079,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. @@ -7106,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": @@ -7145,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/ActivityClient.java b/core/java/android/app/ActivityClient.java index eb4a355c8ae7..605a1fa82254 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -428,11 +428,11 @@ public class ActivityClient { } } - void overridePendingTransition(IBinder token, String packageName, - int enterAnim, int exitAnim) { + void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim, + int backgroundColor) { try { getActivityClientController().overridePendingTransition(token, packageName, - enterAnim, exitAnim); + enterAnim, exitAnim, backgroundColor); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 5e5649f4eadf..e405b60c8daa 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -126,6 +126,12 @@ public class ActivityOptions extends ComponentOptions { public static final String KEY_ANIM_IN_PLACE_RES_ID = "android:activity.animInPlaceRes"; /** + * Custom background color for animation. + * @hide + */ + public static final String KEY_ANIM_BACKGROUND_COLOR = "android:activity.backgroundColor"; + + /** * Bitmap for thumbnail animation. * @hide */ @@ -389,6 +395,7 @@ public class ActivityOptions extends ComponentOptions { private int mCustomEnterResId; private int mCustomExitResId; private int mCustomInPlaceResId; + private int mCustomBackgroundColor; private Bitmap mThumbnail; private int mStartX; private int mStartY; @@ -453,7 +460,27 @@ public class ActivityOptions extends ComponentOptions { */ public static ActivityOptions makeCustomAnimation(Context context, int enterResId, int exitResId) { - return makeCustomAnimation(context, enterResId, exitResId, null, null, null); + return makeCustomAnimation(context, enterResId, exitResId, 0, null, null); + } + + /** + * Create an ActivityOptions specifying a custom animation to run when + * the activity is displayed. + * + * @param context Who is defining this. This is the application that the + * animation resources will be loaded from. + * @param enterResId A resource ID of the animation resource to use for + * the incoming activity. Use 0 for no animation. + * @param exitResId A resource ID of the animation resource to use for + * the outgoing activity. Use 0 for no animation. + * @param backgroundColor The background color to use for the background during the animation if + * the animation requires a background. Set to 0 to not override the default color. + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. + */ + public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context, + int enterResId, int exitResId, int backgroundColor) { + return makeCustomAnimation(context, enterResId, exitResId, backgroundColor, null, null); } /** @@ -477,12 +504,14 @@ public class ActivityOptions extends ComponentOptions { */ @UnsupportedAppUsage public static ActivityOptions makeCustomAnimation(Context context, - int enterResId, int exitResId, Handler handler, OnAnimationStartedListener listener) { + int enterResId, int exitResId, int backgroundColor, Handler handler, + OnAnimationStartedListener listener) { ActivityOptions opts = new ActivityOptions(); opts.mPackageName = context.getPackageName(); opts.mAnimationType = ANIM_CUSTOM; opts.mCustomEnterResId = enterResId; opts.mCustomExitResId = exitResId; + opts.mCustomBackgroundColor = backgroundColor; opts.setOnAnimationStartedListener(handler, listener); return opts; } @@ -510,11 +539,11 @@ public class ActivityOptions extends ComponentOptions { */ @TestApi public static @NonNull ActivityOptions makeCustomAnimation(@NonNull Context context, - int enterResId, int exitResId, @Nullable Handler handler, + int enterResId, int exitResId, int backgroundColor, @Nullable Handler handler, @Nullable OnAnimationStartedListener startedListener, @Nullable OnAnimationFinishedListener finishedListener) { - ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler, - startedListener); + ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, backgroundColor, + handler, startedListener); opts.setOnAnimationFinishedListener(handler, finishedListener); return opts; } @@ -547,8 +576,8 @@ public class ActivityOptions extends ComponentOptions { int enterResId, int exitResId, @Nullable Handler handler, @Nullable OnAnimationStartedListener startedListener, @Nullable OnAnimationFinishedListener finishedListener) { - ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, handler, - startedListener, finishedListener); + ActivityOptions opts = makeCustomAnimation(context, enterResId, exitResId, 0, + handler, startedListener, finishedListener); opts.mOverrideTaskTransition = true; return opts; } @@ -1243,6 +1272,11 @@ public class ActivityOptions extends ComponentOptions { return mCustomInPlaceResId; } + /** @hide */ + public int getCustomBackgroundColor() { + return mCustomBackgroundColor; + } + /** * The thumbnail is copied into a hardware bitmap when it is bundled and sent to the system, so * it should always be backed by a HardwareBuffer on the other end. @@ -1775,6 +1809,7 @@ public class ActivityOptions extends ComponentOptions { case ANIM_CUSTOM: mCustomEnterResId = otherOptions.mCustomEnterResId; mCustomExitResId = otherOptions.mCustomExitResId; + mCustomBackgroundColor = otherOptions.mCustomBackgroundColor; mThumbnail = null; if (mAnimationStartedListener != null) { try { @@ -1862,6 +1897,7 @@ public class ActivityOptions extends ComponentOptions { case ANIM_CUSTOM: b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId); b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId); + b.putInt(KEY_ANIM_BACKGROUND_COLOR, mCustomBackgroundColor); b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener != null ? mAnimationStartedListener.asBinder() : null); break; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3825d8d3177b..876e4014ba45 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; @@ -4591,7 +4592,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 +7813,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 008123407cff..0d1bc05df67b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1327,9 +1327,17 @@ public class AppOpsManager { */ public static final int OP_ESTABLISH_VPN_MANAGER = AppProtoEnums.APP_OP_ESTABLISH_VPN_MANAGER; + /** + * Access restricted settings. + * + * @hide + */ + public static final int OP_ACCESS_RESTRICTED_SETTINGS = + AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 119; + public static final int _NUM_OP = 120; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1784,6 +1792,14 @@ public class AppOpsManager { @SystemApi public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager"; + /** + * Limit user accessing restricted settings. + * + * @hide + */ + public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS = + "android:access_restricted_settings"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2004,6 +2020,7 @@ public class AppOpsManager { OP_NEARBY_WIFI_DEVICES, // OP_NEARBY_WIFI_DEVICES OP_ESTABLISH_VPN_SERVICE, // OP_ESTABLISH_VPN_SERVICE OP_ESTABLISH_VPN_MANAGER, // OP_ESTABLISH_VPN_MANAGER + OP_ACCESS_RESTRICTED_SETTINGS, // OP_ACCESS_RESTRICTED_SETTINGS }; /** @@ -2129,6 +2146,7 @@ public class AppOpsManager { OPSTR_NEARBY_WIFI_DEVICES, OPSTR_ESTABLISH_VPN_SERVICE, OPSTR_ESTABLISH_VPN_MANAGER, + OPSTR_ACCESS_RESTRICTED_SETTINGS, }; /** @@ -2255,6 +2273,7 @@ public class AppOpsManager { "NEARBY_WIFI_DEVICES", "ESTABLISH_VPN_SERVICE", "ESTABLISH_VPN_MANAGER", + "ACCESS_RESTRICTED_SETTINGS", }; /** @@ -2382,6 +2401,7 @@ public class AppOpsManager { Manifest.permission.NEARBY_WIFI_DEVICES, null, // no permission for OP_ESTABLISH_VPN_SERVICE null, // no permission for OP_ESTABLISH_VPN_MANAGER + null, // no permission for OP_ACCESS_RESTRICTED_SETTINGS, }; /** @@ -2509,6 +2529,7 @@ public class AppOpsManager { null, // NEARBY_WIFI_DEVICES null, // ESTABLISH_VPN_SERVICE null, // ESTABLISH_VPN_MANAGER + null, // ACCESS_RESTRICTED_SETTINGS, }; /** @@ -2635,6 +2656,7 @@ public class AppOpsManager { null, // NEARBY_WIFI_DEVICES null, // ESTABLISH_VPN_SERVICE null, // ESTABLISH_VPN_MANAGER + null, // ACCESS_RESTRICTED_SETTINGS, }; /** @@ -2760,6 +2782,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // NEARBY_WIFI_DEVICES AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_SERVICE AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_MANAGER + AppOpsManager.MODE_ALLOWED, // ACCESS_RESTRICTED_SETTINGS, }; /** @@ -2889,6 +2912,7 @@ public class AppOpsManager { false, // NEARBY_WIFI_DEVICES false, // OP_ESTABLISH_VPN_SERVICE false, // OP_ESTABLISH_VPN_MANAGER + true, // ACCESS_RESTRICTED_SETTINGS }; /** 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/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index 83c57c573b82..396e5528ab0c 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -112,7 +112,7 @@ interface IActivityClientController { * calls, so this method should be the same as them to keep the invocation order. */ void overridePendingTransition(in IBinder token, in String packageName, - int enterAnim, int exitAnim); + int enterAnim, int exitAnim, int backgroundColor); int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName); /** See {@link android.app.Activity#setDisablePreviewScreenshots}. */ 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/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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index dd832c81d529..bbdd7050cb07 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1034,15 +1034,14 @@ public final class SystemServiceRegistry { }}); registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class, - new CachedServiceFetcher<PersistentDataBlockManager>() { + new StaticServiceFetcher<PersistentDataBlockManager>() { @Override - public PersistentDataBlockManager createService(ContextImpl ctx) - throws ServiceNotFoundException { + public PersistentDataBlockManager createService() throws ServiceNotFoundException { IBinder b = ServiceManager.getServiceOrThrow(Context.PERSISTENT_DATA_BLOCK_SERVICE); IPersistentDataBlockService persistentDataBlockService = IPersistentDataBlockService.Stub.asInterface(b); if (persistentDataBlockService != null) { - return new PersistentDataBlockManager(ctx, persistentDataBlockService); + return new PersistentDataBlockManager(persistentDataBlockService); } else { // not supported return null; 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..9ac4030c73a7 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) @@ -14577,6 +14623,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi public void setDeviceOwnerType(@NonNull ComponentName admin, @DeviceOwnerType int deviceOwnerType) { throwIfParentInstance("setDeviceOwnerType"); @@ -14600,6 +14647,7 @@ public class DevicePolicyManager { * * @hide */ + @TestApi @DeviceOwnerType public int getDeviceOwnerType(@NonNull ComponentName admin) { throwIfParentInstance("getDeviceOwnerType"); diff --git a/core/java/android/app/smartspace/SmartspaceTarget.java b/core/java/android/app/smartspace/SmartspaceTarget.java index 8e9853575c31..78f51be78828 100644 --- a/core/java/android/app/smartspace/SmartspaceTarget.java +++ b/core/java/android/app/smartspace/SmartspaceTarget.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.app.smartspace.uitemplatedata.SmartspaceDefaultUiTemplateData; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; import android.net.Uri; @@ -131,6 +132,9 @@ public final class SmartspaceTarget implements Parcelable { @Nullable private final AppWidgetProviderInfo mWidget; + @Nullable + private final SmartspaceDefaultUiTemplateData mTemplateData; + public static final int FEATURE_UNDEFINED = 0; public static final int FEATURE_WEATHER = 1; public static final int FEATURE_CALENDAR = 2; @@ -189,6 +193,32 @@ public final class SmartspaceTarget implements Parcelable { public @interface FeatureType { } + public static final int UI_TEMPLATE_UNDEFINED = 0; + public static final int UI_TEMPLATE_DEFAULT = 1; + public static final int UI_TEMPLATE_SUB_IMAGE = 2; + public static final int UI_TEMPLATE_SUB_LIST = 3; + public static final int UI_TEMPLATE_CAROUSEL = 4; + public static final int UI_TEMPLATE_HEAD_TO_HEAD = 5; + public static final int UI_TEMPLATE_COMBINED_CARDS = 6; + public static final int UI_TEMPLATE_SUB_CARD = 7; + + /** + * @hide + */ + @IntDef(prefix = {"UI_TEMPLATE_"}, value = { + UI_TEMPLATE_UNDEFINED, + UI_TEMPLATE_DEFAULT, + UI_TEMPLATE_SUB_IMAGE, + UI_TEMPLATE_SUB_LIST, + UI_TEMPLATE_CAROUSEL, + UI_TEMPLATE_HEAD_TO_HEAD, + UI_TEMPLATE_COMBINED_CARDS, + UI_TEMPLATE_SUB_CARD + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UiTemplateType { + } + private SmartspaceTarget(Parcel in) { this.mSmartspaceTargetId = in.readString(); this.mHeaderAction = in.readTypedObject(SmartspaceAction.CREATOR); @@ -207,6 +237,7 @@ public final class SmartspaceTarget implements Parcelable { this.mAssociatedSmartspaceTargetId = in.readString(); this.mSliceUri = in.readTypedObject(Uri.CREATOR); this.mWidget = in.readTypedObject(AppWidgetProviderInfo.CREATOR); + this.mTemplateData = in.readTypedObject(SmartspaceDefaultUiTemplateData.CREATOR); } private SmartspaceTarget(String smartspaceTargetId, @@ -217,7 +248,7 @@ public final class SmartspaceTarget implements Parcelable { boolean shouldShowExpanded, String sourceNotificationKey, ComponentName componentName, UserHandle userHandle, String associatedSmartspaceTargetId, Uri sliceUri, - AppWidgetProviderInfo widget) { + AppWidgetProviderInfo widget, SmartspaceDefaultUiTemplateData templateData) { mSmartspaceTargetId = smartspaceTargetId; mHeaderAction = headerAction; mBaseAction = baseAction; @@ -235,6 +266,7 @@ public final class SmartspaceTarget implements Parcelable { mAssociatedSmartspaceTargetId = associatedSmartspaceTargetId; mSliceUri = sliceUri; mWidget = widget; + mTemplateData = templateData; } /** @@ -371,6 +403,14 @@ public final class SmartspaceTarget implements Parcelable { } /** + * Returns the UI template data. + */ + @Nullable + public SmartspaceDefaultUiTemplateData getTemplateData() { + return mTemplateData; + } + + /** * @see Parcelable.Creator */ @NonNull @@ -405,6 +445,7 @@ public final class SmartspaceTarget implements Parcelable { dest.writeString(this.mAssociatedSmartspaceTargetId); dest.writeTypedObject(this.mSliceUri, flags); dest.writeTypedObject(this.mWidget, flags); + dest.writeTypedObject(this.mTemplateData, flags); } @Override @@ -432,6 +473,7 @@ public final class SmartspaceTarget implements Parcelable { + ", mAssociatedSmartspaceTargetId='" + mAssociatedSmartspaceTargetId + '\'' + ", mSliceUri=" + mSliceUri + ", mWidget=" + mWidget + + ", mTemplateData=" + mTemplateData + '}'; } @@ -457,7 +499,8 @@ public final class SmartspaceTarget implements Parcelable { && Objects.equals(mAssociatedSmartspaceTargetId, that.mAssociatedSmartspaceTargetId) && Objects.equals(mSliceUri, that.mSliceUri) - && Objects.equals(mWidget, that.mWidget); + && Objects.equals(mWidget, that.mWidget) + && Objects.equals(mTemplateData, that.mTemplateData); } @Override @@ -465,7 +508,7 @@ public final class SmartspaceTarget implements Parcelable { return Objects.hash(mSmartspaceTargetId, mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle, - mAssociatedSmartspaceTargetId, mSliceUri, mWidget); + mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData); } /** @@ -476,6 +519,9 @@ public final class SmartspaceTarget implements Parcelable { @SystemApi public static final class Builder { private final String mSmartspaceTargetId; + private final ComponentName mComponentName; + private final UserHandle mUserHandle; + private SmartspaceAction mHeaderAction; private SmartspaceAction mBaseAction; private long mCreationTimeMillis; @@ -487,11 +533,10 @@ public final class SmartspaceTarget implements Parcelable { private boolean mSensitive; private boolean mShouldShowExpanded; private String mSourceNotificationKey; - private final ComponentName mComponentName; - private final UserHandle mUserHandle; private String mAssociatedSmartspaceTargetId; private Uri mSliceUri; private AppWidgetProviderInfo mWidget; + private SmartspaceDefaultUiTemplateData mTemplateData; /** * A builder for {@link SmartspaceTarget}. @@ -640,6 +685,16 @@ public final class SmartspaceTarget implements Parcelable { } /** + * Sets the UI template data. + */ + @NonNull + public Builder setTemplateData( + @Nullable SmartspaceDefaultUiTemplateData templateData) { + mTemplateData = templateData; + return this; + } + + /** * Builds a new {@link SmartspaceTarget}. * * @throws IllegalStateException when non null fields are set as null. @@ -655,7 +710,7 @@ public final class SmartspaceTarget implements Parcelable { mHeaderAction, mBaseAction, mCreationTimeMillis, mExpiryTimeMillis, mScore, mActionChips, mIconGrid, mFeatureType, mSensitive, mShouldShowExpanded, mSourceNotificationKey, mComponentName, mUserHandle, - mAssociatedSmartspaceTargetId, mSliceUri, mWidget); + mAssociatedSmartspaceTargetId, mSliceUri, mWidget, mTemplateData); } } } diff --git a/core/java/android/app/smartspace/SmartspaceUtils.java b/core/java/android/app/smartspace/SmartspaceUtils.java new file mode 100644 index 000000000000..f058ffa5a04b --- /dev/null +++ b/core/java/android/app/smartspace/SmartspaceUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.smartspace; + +import android.annotation.Nullable; + +/** + * Utilities for Smartspace data. + * + * @hide + */ +public final class SmartspaceUtils { + + private SmartspaceUtils() { + } + + /** Returns true if the passed-in {@link CharSequence}s are equal. */ + public static boolean isEqual(@Nullable CharSequence cs1, @Nullable CharSequence cs2) { + if ((cs1 == null && cs2 != null) || (cs1 != null && cs2 == null)) return false; + if (cs1 == null && cs2 == null) return true; + return cs1.toString().contentEquals(cs2); + } +} diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java new file mode 100644 index 000000000000..c4c4fdef67f9 --- /dev/null +++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCarouselUiTemplateData.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.smartspace.uitemplatedata; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.smartspace.SmartspaceTarget; +import android.app.smartspace.SmartspaceUtils; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.List; +import java.util.Objects; + +/** + * Holds all the relevant data needed to render a Smartspace card with the carousel Ui Template. + * + * @hide + */ +@SystemApi +public final class SmartspaceCarouselUiTemplateData extends SmartspaceDefaultUiTemplateData { + + /** Lists of {@link CarouselItem}. */ + @NonNull + private final List<CarouselItem> mCarouselItems; + + /** Tap action for the entire carousel secondary card, including the blank space */ + @Nullable + private final SmartspaceTapAction mCarouselAction; + + SmartspaceCarouselUiTemplateData(@NonNull Parcel in) { + super(in); + mCarouselItems = in.createTypedArrayList(CarouselItem.CREATOR); + mCarouselAction = in.readTypedObject(SmartspaceTapAction.CREATOR); + } + + private SmartspaceCarouselUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType, + @Nullable CharSequence titleText, + @Nullable SmartspaceIcon titleIcon, + @Nullable CharSequence subtitleText, + @Nullable SmartspaceIcon subTitleIcon, + @Nullable SmartspaceTapAction primaryTapAction, + @Nullable CharSequence supplementalSubtitleText, + @Nullable SmartspaceIcon supplementalSubtitleIcon, + @Nullable SmartspaceTapAction supplementalSubtitleTapAction, + @Nullable CharSequence supplementalAlarmText, + @NonNull List<CarouselItem> carouselItems, + @Nullable SmartspaceTapAction carouselAction) { + super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, + supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, + supplementalAlarmText); + mCarouselItems = carouselItems; + mCarouselAction = carouselAction; + } + + @NonNull + public List<CarouselItem> getCarouselItems() { + return mCarouselItems; + } + + @Nullable + public SmartspaceTapAction getCarouselAction() { + return mCarouselAction; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<SmartspaceCarouselUiTemplateData> CREATOR = + new Creator<SmartspaceCarouselUiTemplateData>() { + @Override + public SmartspaceCarouselUiTemplateData createFromParcel(Parcel in) { + return new SmartspaceCarouselUiTemplateData(in); + } + + @Override + public SmartspaceCarouselUiTemplateData[] newArray(int size) { + return new SmartspaceCarouselUiTemplateData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeTypedList(mCarouselItems); + out.writeTypedObject(mCarouselAction, flags); + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SmartspaceCarouselUiTemplateData)) return false; + if (!super.equals(o)) return false; + SmartspaceCarouselUiTemplateData that = (SmartspaceCarouselUiTemplateData) o; + return mCarouselItems.equals(that.mCarouselItems) && Objects.equals(mCarouselAction, + that.mCarouselAction); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mCarouselItems, mCarouselAction); + } + + @Override + public String toString() { + return super.toString() + " + SmartspaceCarouselUiTemplateData{" + + "mCarouselItems=" + mCarouselItems + + ", mCarouselActions=" + mCarouselAction + + '}'; + } + + /** + * A builder for {@link SmartspaceCarouselUiTemplateData} object. + * + * @hide + */ + @SystemApi + public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder { + + private final List<CarouselItem> mCarouselItems; + private SmartspaceTapAction mCarouselAction; + + /** + * A builder for {@link SmartspaceCarouselUiTemplateData}. + */ + public Builder(@NonNull List<CarouselItem> carouselItems) { + super(SmartspaceTarget.UI_TEMPLATE_CAROUSEL); + mCarouselItems = Objects.requireNonNull(carouselItems); + } + + /** + * Sets the card tap action. + */ + @NonNull + public Builder setCarouselAction(@NonNull SmartspaceTapAction carouselAction) { + mCarouselAction = carouselAction; + return this; + } + + /** + * Builds a new SmartspaceCarouselUiTemplateData instance. + * + * @throws IllegalStateException if the carousel data is invalid. + */ + @NonNull + public SmartspaceCarouselUiTemplateData build() { + if (mCarouselItems.isEmpty()) { + throw new IllegalStateException("Carousel data is empty"); + } + return new SmartspaceCarouselUiTemplateData(getTemplateType(), getTitleText(), + getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(), + getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), + getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), + mCarouselItems, + mCarouselAction); + } + } + + /** Holds all the relevant data needed to render a carousel item. */ + public static final class CarouselItem implements Parcelable { + + /** Text which is above the image item. */ + @Nullable + private final CharSequence mUpperText; + + /** Image item. Can be empty. */ + @Nullable + private final SmartspaceIcon mImage; + + /** Text which is under the image item. */ + @Nullable + private final CharSequence mLowerText; + + /** + * Tap action for this {@link CarouselItem} instance. {@code mCarouselAction} is used if not + * being set. + */ + @Nullable + private final SmartspaceTapAction mTapAction; + + CarouselItem(@NonNull Parcel in) { + mUpperText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mImage = in.readTypedObject(SmartspaceIcon.CREATOR); + mLowerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR); + } + + private CarouselItem(@Nullable CharSequence upperText, @Nullable SmartspaceIcon image, + @Nullable CharSequence lowerText, @Nullable SmartspaceTapAction tapAction) { + mUpperText = upperText; + mImage = image; + mLowerText = lowerText; + mTapAction = tapAction; + } + + @Nullable + public CharSequence getUpperText() { + return mUpperText; + } + + @Nullable + public SmartspaceIcon getImage() { + return mImage; + } + + @Nullable + public CharSequence getLowerText() { + return mLowerText; + } + + @Nullable + public SmartspaceTapAction getTapAction() { + return mTapAction; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<CarouselItem> CREATOR = + new Creator<CarouselItem>() { + @Override + public CarouselItem createFromParcel(Parcel in) { + return new CarouselItem(in); + } + + @Override + public CarouselItem[] newArray(int size) { + return new CarouselItem[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + TextUtils.writeToParcel(mUpperText, out, flags); + out.writeTypedObject(mImage, flags); + TextUtils.writeToParcel(mLowerText, out, flags); + out.writeTypedObject(mTapAction, flags); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CarouselItem)) return false; + CarouselItem that = (CarouselItem) o; + return SmartspaceUtils.isEqual(mUpperText, that.mUpperText) && Objects.equals( + mImage, + that.mImage) && SmartspaceUtils.isEqual(mLowerText, that.mLowerText) + && Objects.equals(mTapAction, that.mTapAction); + } + + @Override + public int hashCode() { + return Objects.hash(mUpperText, mImage, mLowerText, mTapAction); + } + + @Override + public String toString() { + return "CarouselItem{" + + "mUpperText=" + mUpperText + + ", mImage=" + mImage + + ", mLowerText=" + mLowerText + + ", mTapAction=" + mTapAction + + '}'; + } + + /** + * A builder for {@link CarouselItem} object. + * + * @hide + */ + @SystemApi + public static final class Builder { + + private CharSequence mUpperText; + private SmartspaceIcon mImage; + private CharSequence mLowerText; + private SmartspaceTapAction mTapAction; + + /** + * Sets the upper text. + */ + @NonNull + public Builder setUpperText(@Nullable CharSequence upperText) { + mUpperText = upperText; + return this; + } + + /** + * Sets the image. + */ + @NonNull + public Builder setImage(@Nullable SmartspaceIcon image) { + mImage = image; + return this; + } + + + /** + * Sets the lower text. + */ + @NonNull + public Builder setLowerText(@Nullable CharSequence lowerText) { + mLowerText = lowerText; + return this; + } + + /** + * Sets the tap action. + */ + @NonNull + public Builder setTapAction(@Nullable SmartspaceTapAction tapAction) { + mTapAction = tapAction; + return this; + } + + /** + * Builds a new CarouselItem instance. + * + * @throws IllegalStateException if all the rendering data is empty. + */ + @NonNull + public CarouselItem build() { + if (TextUtils.isEmpty(mUpperText) && mImage == null && TextUtils.isEmpty( + mLowerText)) { + throw new IllegalStateException("Carousel data is empty"); + } + return new CarouselItem(mUpperText, mImage, mLowerText, mTapAction); + } + } + } +} diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.java new file mode 100644 index 000000000000..7e2f74eac4fe --- /dev/null +++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceCombinedCardsUiTemplateData.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 android.app.smartspace.uitemplatedata; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.smartspace.SmartspaceTarget; +import android.os.Parcel; + +import java.util.List; +import java.util.Objects; + +/** + * Holds all the relevant data needed to render a Smartspace card with the combined-card Ui + * Template. + * + * We only support 1 sub-list card combined with 1 carousel card. And we may expand our supported + * combinations in the future. + * + * @hide + */ +@SystemApi +public final class SmartspaceCombinedCardsUiTemplateData extends SmartspaceDefaultUiTemplateData { + + /** A list of secondary cards. */ + @NonNull + private final List<SmartspaceDefaultUiTemplateData> mCombinedCardDataList; + + SmartspaceCombinedCardsUiTemplateData(@NonNull Parcel in) { + super(in); + mCombinedCardDataList = in.createTypedArrayList(SmartspaceDefaultUiTemplateData.CREATOR); + } + + private SmartspaceCombinedCardsUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType, + @Nullable CharSequence titleText, + @Nullable SmartspaceIcon titleIcon, + @Nullable CharSequence subtitleText, + @Nullable SmartspaceIcon subTitleIcon, + @Nullable SmartspaceTapAction primaryTapAction, + @Nullable CharSequence supplementalSubtitleText, + @Nullable SmartspaceIcon supplementalSubtitleIcon, + @Nullable SmartspaceTapAction supplementalSubtitleTapAction, + @Nullable CharSequence supplementalAlarmText, + @NonNull List<SmartspaceDefaultUiTemplateData> combinedCardDataList) { + super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, + supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, + supplementalAlarmText); + mCombinedCardDataList = combinedCardDataList; + } + + @NonNull + public List<SmartspaceDefaultUiTemplateData> getCombinedCardDataList() { + return mCombinedCardDataList; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<SmartspaceCombinedCardsUiTemplateData> CREATOR = + new Creator<SmartspaceCombinedCardsUiTemplateData>() { + @Override + public SmartspaceCombinedCardsUiTemplateData createFromParcel(Parcel in) { + return new SmartspaceCombinedCardsUiTemplateData(in); + } + + @Override + public SmartspaceCombinedCardsUiTemplateData[] newArray(int size) { + return new SmartspaceCombinedCardsUiTemplateData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeTypedList(mCombinedCardDataList); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SmartspaceCombinedCardsUiTemplateData)) return false; + if (!super.equals(o)) return false; + SmartspaceCombinedCardsUiTemplateData that = (SmartspaceCombinedCardsUiTemplateData) o; + return mCombinedCardDataList.equals(that.mCombinedCardDataList); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mCombinedCardDataList); + } + + @Override + public String toString() { + return super.toString() + " + SmartspaceCombinedCardsUiTemplateData{" + + "mCombinedCardDataList=" + mCombinedCardDataList + + '}'; + } + + /** + * A builder for {@link SmartspaceCombinedCardsUiTemplateData} object. + * + * @hide + */ + @SystemApi + public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder { + + private final List<SmartspaceDefaultUiTemplateData> mCombinedCardDataList; + + /** + * A builder for {@link SmartspaceCombinedCardsUiTemplateData}. + */ + public Builder(@NonNull List<SmartspaceDefaultUiTemplateData> combinedCardDataList) { + super(SmartspaceTarget.UI_TEMPLATE_COMBINED_CARDS); + mCombinedCardDataList = Objects.requireNonNull(combinedCardDataList); + } + + /** + * Builds a new SmartspaceCombinedCardsUiTemplateData instance. + * + * @throws IllegalStateException if any required non-null field is null + */ + @NonNull + public SmartspaceCombinedCardsUiTemplateData build() { + if (mCombinedCardDataList == null) { + throw new IllegalStateException("Please assign a value to all @NonNull args."); + } + return new SmartspaceCombinedCardsUiTemplateData(getTemplateType(), getTitleText(), + getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(), + getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), + getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), + mCombinedCardDataList); + } + } +} diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java new file mode 100644 index 000000000000..742d5c9bdc0e --- /dev/null +++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceDefaultUiTemplateData.java @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.smartspace.uitemplatedata; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.app.smartspace.SmartspaceTarget.UiTemplateType; +import android.app.smartspace.SmartspaceUtils; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * Holds all the relevant data needed to render a Smartspace card with the default Ui Template. + * + * @hide + */ +@SystemApi +@SuppressLint("ParcelNotFinal") +public class SmartspaceDefaultUiTemplateData implements Parcelable { + + /** + * {@link UiTemplateType} indicating the template type of this template data. + * + * @see UiTemplateType + */ + @UiTemplateType + private final int mTemplateType; + + /** + * Title text and title icon are shown at the first row. When both are absent, the date view + * will be used, which has its own tap action applied to the title area. + */ + @Nullable + private final CharSequence mTitleText; + + @Nullable + private final SmartspaceIcon mTitleIcon; + + /** Subtitle text and icon are shown at the second row. */ + @Nullable + private final CharSequence mSubtitleText; + + @Nullable + private final SmartspaceIcon mSubTitleIcon; + + /** + * Primary tap action for the entire card, including the blank spaces, except: 1. When title is + * absent, the date view's default tap action is used; 2. Supplemental subtitle uses its own tap + * action if being set; 3. Secondary card uses its own tap action if being set. + */ + @Nullable + private final SmartspaceTapAction mPrimaryTapAction; + + /** + * Supplemental subtitle text and icon are shown at the second row following the subtitle text. + * Mainly used for weather info on non-weather card. + */ + @Nullable + private final CharSequence mSupplementalSubtitleText; + + @Nullable + private final SmartspaceIcon mSupplementalSubtitleIcon; + + /** + * Tap action for the supplemental subtitle's text and icon. Will use the primary tap action if + * not being set. + */ + @Nullable + private final SmartspaceTapAction mSupplementalSubtitleTapAction; + + /** + * Supplemental alarm text is specifically used for holiday alarm, which is appended to "next + * alarm". + */ + @Nullable + private final CharSequence mSupplementalAlarmText; + + SmartspaceDefaultUiTemplateData(@NonNull Parcel in) { + mTemplateType = in.readInt(); + mTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mTitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR); + mSubtitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mSubTitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR); + mPrimaryTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR); + mSupplementalSubtitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mSupplementalSubtitleIcon = in.readTypedObject(SmartspaceIcon.CREATOR); + mSupplementalSubtitleTapAction = in.readTypedObject(SmartspaceTapAction.CREATOR); + mSupplementalAlarmText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + } + + /** + * Should ONLY used by subclasses. For the general instance creation, please use + * SmartspaceDefaultUiTemplateData.Builder. + */ + SmartspaceDefaultUiTemplateData(@UiTemplateType int templateType, + @Nullable CharSequence titleText, + @Nullable SmartspaceIcon titleIcon, + @Nullable CharSequence subtitleText, + @Nullable SmartspaceIcon subTitleIcon, + @Nullable SmartspaceTapAction primaryTapAction, + @Nullable CharSequence supplementalSubtitleText, + @Nullable SmartspaceIcon supplementalSubtitleIcon, + @Nullable SmartspaceTapAction supplementalSubtitleTapAction, + @Nullable CharSequence supplementalAlarmText) { + mTemplateType = templateType; + mTitleText = titleText; + mTitleIcon = titleIcon; + mSubtitleText = subtitleText; + mSubTitleIcon = subTitleIcon; + mPrimaryTapAction = primaryTapAction; + mSupplementalSubtitleText = supplementalSubtitleText; + mSupplementalSubtitleIcon = supplementalSubtitleIcon; + mSupplementalSubtitleTapAction = supplementalSubtitleTapAction; + mSupplementalAlarmText = supplementalAlarmText; + } + + @UiTemplateType + public int getTemplateType() { + return mTemplateType; + } + + @Nullable + public CharSequence getTitleText() { + return mTitleText; + } + + @Nullable + public SmartspaceIcon getTitleIcon() { + return mTitleIcon; + } + + @Nullable + public CharSequence getSubtitleText() { + return mSubtitleText; + } + + @Nullable + public SmartspaceIcon getSubTitleIcon() { + return mSubTitleIcon; + } + + @Nullable + public CharSequence getSupplementalSubtitleText() { + return mSupplementalSubtitleText; + } + + @Nullable + public SmartspaceIcon getSupplementalSubtitleIcon() { + return mSupplementalSubtitleIcon; + } + + @Nullable + public SmartspaceTapAction getPrimaryTapAction() { + return mPrimaryTapAction; + } + + @Nullable + public SmartspaceTapAction getSupplementalSubtitleTapAction() { + return mSupplementalSubtitleTapAction; + } + + @Nullable + public CharSequence getSupplementalAlarmText() { + return mSupplementalAlarmText; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<SmartspaceDefaultUiTemplateData> CREATOR = + new Creator<SmartspaceDefaultUiTemplateData>() { + @Override + public SmartspaceDefaultUiTemplateData createFromParcel(Parcel in) { + return new SmartspaceDefaultUiTemplateData(in); + } + + @Override + public SmartspaceDefaultUiTemplateData[] newArray(int size) { + return new SmartspaceDefaultUiTemplateData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeInt(mTemplateType); + TextUtils.writeToParcel(mTitleText, out, flags); + out.writeTypedObject(mTitleIcon, flags); + TextUtils.writeToParcel(mSubtitleText, out, flags); + out.writeTypedObject(mSubTitleIcon, flags); + out.writeTypedObject(mPrimaryTapAction, flags); + TextUtils.writeToParcel(mSupplementalSubtitleText, out, flags); + out.writeTypedObject(mSupplementalSubtitleIcon, flags); + out.writeTypedObject(mSupplementalSubtitleTapAction, flags); + TextUtils.writeToParcel(mSupplementalAlarmText, out, flags); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SmartspaceDefaultUiTemplateData)) return false; + SmartspaceDefaultUiTemplateData that = (SmartspaceDefaultUiTemplateData) o; + return mTemplateType == that.mTemplateType && SmartspaceUtils.isEqual(mTitleText, + that.mTitleText) + && Objects.equals(mTitleIcon, that.mTitleIcon) + && SmartspaceUtils.isEqual(mSubtitleText, that.mSubtitleText) + && Objects.equals(mSubTitleIcon, that.mSubTitleIcon) + && Objects.equals(mPrimaryTapAction, that.mPrimaryTapAction) + && SmartspaceUtils.isEqual(mSupplementalSubtitleText, + that.mSupplementalSubtitleText) + && Objects.equals(mSupplementalSubtitleIcon, that.mSupplementalSubtitleIcon) + && Objects.equals(mSupplementalSubtitleTapAction, + that.mSupplementalSubtitleTapAction) + && SmartspaceUtils.isEqual(mSupplementalAlarmText, that.mSupplementalAlarmText); + } + + @Override + public int hashCode() { + return Objects.hash(mTemplateType, mTitleText, mTitleIcon, mSubtitleText, mSubTitleIcon, + mPrimaryTapAction, mSupplementalSubtitleText, mSupplementalSubtitleIcon, + mSupplementalSubtitleTapAction, mSupplementalAlarmText); + } + + @Override + public String toString() { + return "SmartspaceDefaultUiTemplateData{" + + "mTemplateType=" + mTemplateType + + ", mTitleText=" + mTitleText + + ", mTitleIcon=" + mTitleIcon + + ", mSubtitleText=" + mSubtitleText + + ", mSubTitleIcon=" + mSubTitleIcon + + ", mPrimaryTapAction=" + mPrimaryTapAction + + ", mSupplementalSubtitleText=" + mSupplementalSubtitleText + + ", mSupplementalSubtitleIcon=" + mSupplementalSubtitleIcon + + ", mSupplementalSubtitleTapAction=" + mSupplementalSubtitleTapAction + + ", mSupplementalAlarmText=" + mSupplementalAlarmText + + '}'; + } + + /** + * A builder for {@link SmartspaceDefaultUiTemplateData} object. + * + * @hide + */ + @SystemApi + @SuppressLint("StaticFinalBuilder") + public static class Builder { + @UiTemplateType + private final int mTemplateType; + private CharSequence mTitleText; + private SmartspaceIcon mTitleIcon; + private CharSequence mSubtitleText; + private SmartspaceIcon mSubTitleIcon; + private SmartspaceTapAction mPrimaryTapAction; + private CharSequence mSupplementalSubtitleText; + private SmartspaceIcon mSupplementalSubtitleIcon; + private SmartspaceTapAction mSupplementalSubtitleTapAction; + private CharSequence mSupplementalAlarmText; + + /** + * A builder for {@link SmartspaceDefaultUiTemplateData}. + * + * @param templateType the {@link UiTemplateType} of this template data. + */ + public Builder(@UiTemplateType int templateType) { + mTemplateType = templateType; + } + + /** Should ONLY be used by the subclasses */ + @UiTemplateType + @SuppressLint("GetterOnBuilder") + int getTemplateType() { + return mTemplateType; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + CharSequence getTitleText() { + return mTitleText; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + SmartspaceIcon getTitleIcon() { + return mTitleIcon; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + CharSequence getSubtitleText() { + return mSubtitleText; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + SmartspaceIcon getSubTitleIcon() { + return mSubTitleIcon; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + SmartspaceTapAction getPrimaryTapAction() { + return mPrimaryTapAction; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + CharSequence getSupplementalSubtitleText() { + return mSupplementalSubtitleText; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + SmartspaceIcon getSupplementalSubtitleIcon() { + return mSupplementalSubtitleIcon; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + SmartspaceTapAction getSupplementalSubtitleTapAction() { + return mSupplementalSubtitleTapAction; + } + + /** Should ONLY be used by the subclasses */ + @Nullable + @SuppressLint("GetterOnBuilder") + CharSequence getSupplementalAlarmText() { + return mSupplementalAlarmText; + } + + /** + * Sets the card title. + */ + @NonNull + public Builder setTitleText(@NonNull CharSequence titleText) { + mTitleText = titleText; + return this; + } + + /** + * Sets the card title icon. + */ + @NonNull + public Builder setTitleIcon(@NonNull SmartspaceIcon titleIcon) { + mTitleIcon = titleIcon; + return this; + } + + /** + * Sets the card subtitle. + */ + @NonNull + public Builder setSubtitleText(@NonNull CharSequence subtitleText) { + mSubtitleText = subtitleText; + return this; + } + + /** + * Sets the card subtitle icon. + */ + @NonNull + public Builder setSubTitleIcon(@NonNull SmartspaceIcon subTitleIcon) { + mSubTitleIcon = subTitleIcon; + return this; + } + + /** + * Sets the card primary tap action. + */ + @NonNull + public Builder setPrimaryTapAction(@NonNull SmartspaceTapAction primaryTapAction) { + mPrimaryTapAction = primaryTapAction; + return this; + } + + /** + * Sets the supplemental subtitle text. + */ + @NonNull + public Builder setSupplementalSubtitleText(@NonNull CharSequence supplementalSubtitleText) { + mSupplementalSubtitleText = supplementalSubtitleText; + return this; + } + + /** + * Sets the supplemental subtitle icon. + */ + @NonNull + public Builder setSupplementalSubtitleIcon( + @NonNull SmartspaceIcon supplementalSubtitleIcon) { + mSupplementalSubtitleIcon = supplementalSubtitleIcon; + return this; + } + + /** + * Sets the supplemental subtitle tap action. {@code mPrimaryTapAction} will be used if not + * being + * set. + */ + @NonNull + public Builder setSupplementalSubtitleTapAction( + @NonNull SmartspaceTapAction supplementalSubtitleTapAction) { + mSupplementalSubtitleTapAction = supplementalSubtitleTapAction; + return this; + } + + /** + * Sets the supplemental alarm text. + */ + @NonNull + public Builder setSupplementalAlarmText(@NonNull CharSequence supplementalAlarmText) { + mSupplementalAlarmText = supplementalAlarmText; + return this; + } + + /** + * Builds a new SmartspaceDefaultUiTemplateData instance. + */ + @NonNull + public SmartspaceDefaultUiTemplateData build() { + return new SmartspaceDefaultUiTemplateData(mTemplateType, mTitleText, mTitleIcon, + mSubtitleText, mSubTitleIcon, mPrimaryTapAction, mSupplementalSubtitleText, + mSupplementalSubtitleIcon, mSupplementalSubtitleTapAction, + mSupplementalAlarmText); + } + } +} diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java new file mode 100644 index 000000000000..c76af27e7f16 --- /dev/null +++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceHeadToHeadUiTemplateData.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.smartspace.uitemplatedata; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.smartspace.SmartspaceTarget; +import android.app.smartspace.SmartspaceUtils; +import android.os.Parcel; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * Holds all the relevant data needed to render a Smartspace card with the head-to-head Ui Template. + * + * @hide + */ +@SystemApi +public final class SmartspaceHeadToHeadUiTemplateData extends SmartspaceDefaultUiTemplateData { + + @Nullable + private final CharSequence mHeadToHeadTitle; + @Nullable + private final SmartspaceIcon mHeadToHeadFirstCompetitorIcon; + @Nullable + private final SmartspaceIcon mHeadToHeadSecondCompetitorIcon; + @Nullable + private final CharSequence mHeadToHeadFirstCompetitorText; + @Nullable + private final CharSequence mHeadToHeadSecondCompetitorText; + + /** Tap action for the head-to-head secondary card. */ + @Nullable + private final SmartspaceTapAction mHeadToHeadAction; + + SmartspaceHeadToHeadUiTemplateData(@NonNull Parcel in) { + super(in); + mHeadToHeadTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mHeadToHeadFirstCompetitorIcon = in.readTypedObject(SmartspaceIcon.CREATOR); + mHeadToHeadSecondCompetitorIcon = in.readTypedObject(SmartspaceIcon.CREATOR); + mHeadToHeadFirstCompetitorText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mHeadToHeadSecondCompetitorText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mHeadToHeadAction = in.readTypedObject(SmartspaceTapAction.CREATOR); + } + + private SmartspaceHeadToHeadUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType, + @Nullable CharSequence titleText, + @Nullable SmartspaceIcon titleIcon, + @Nullable CharSequence subtitleText, + @Nullable SmartspaceIcon subTitleIcon, + @Nullable SmartspaceTapAction primaryTapAction, + @Nullable CharSequence supplementalSubtitleText, + @Nullable SmartspaceIcon supplementalSubtitleIcon, + @Nullable SmartspaceTapAction supplementalSubtitleTapAction, + @Nullable CharSequence supplementalAlarmText, + @Nullable CharSequence headToHeadTitle, + @Nullable SmartspaceIcon headToHeadFirstCompetitorIcon, + @Nullable SmartspaceIcon headToHeadSecondCompetitorIcon, + @Nullable CharSequence headToHeadFirstCompetitorText, + @Nullable CharSequence headToHeadSecondCompetitorText, + @Nullable SmartspaceTapAction headToHeadAction) { + super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, + supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, + supplementalAlarmText); + mHeadToHeadTitle = headToHeadTitle; + mHeadToHeadFirstCompetitorIcon = headToHeadFirstCompetitorIcon; + mHeadToHeadSecondCompetitorIcon = headToHeadSecondCompetitorIcon; + mHeadToHeadFirstCompetitorText = headToHeadFirstCompetitorText; + mHeadToHeadSecondCompetitorText = headToHeadSecondCompetitorText; + mHeadToHeadAction = headToHeadAction; + } + + @Nullable + public CharSequence getHeadToHeadTitle() { + return mHeadToHeadTitle; + } + + @Nullable + public SmartspaceIcon getHeadToHeadFirstCompetitorIcon() { + return mHeadToHeadFirstCompetitorIcon; + } + + @Nullable + public SmartspaceIcon getHeadToHeadSecondCompetitorIcon() { + return mHeadToHeadSecondCompetitorIcon; + } + + @Nullable + public CharSequence getHeadToHeadFirstCompetitorText() { + return mHeadToHeadFirstCompetitorText; + } + + @Nullable + public CharSequence getHeadToHeadSecondCompetitorText() { + return mHeadToHeadSecondCompetitorText; + } + + @Nullable + public SmartspaceTapAction getHeadToHeadAction() { + return mHeadToHeadAction; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<SmartspaceHeadToHeadUiTemplateData> CREATOR = + new Creator<SmartspaceHeadToHeadUiTemplateData>() { + @Override + public SmartspaceHeadToHeadUiTemplateData createFromParcel(Parcel in) { + return new SmartspaceHeadToHeadUiTemplateData(in); + } + + @Override + public SmartspaceHeadToHeadUiTemplateData[] newArray(int size) { + return new SmartspaceHeadToHeadUiTemplateData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + super.writeToParcel(out, flags); + TextUtils.writeToParcel(mHeadToHeadTitle, out, flags); + out.writeTypedObject(mHeadToHeadFirstCompetitorIcon, flags); + out.writeTypedObject(mHeadToHeadSecondCompetitorIcon, flags); + TextUtils.writeToParcel(mHeadToHeadFirstCompetitorText, out, flags); + TextUtils.writeToParcel(mHeadToHeadSecondCompetitorText, out, flags); + out.writeTypedObject(mHeadToHeadAction, flags); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SmartspaceHeadToHeadUiTemplateData)) return false; + if (!super.equals(o)) return false; + SmartspaceHeadToHeadUiTemplateData that = (SmartspaceHeadToHeadUiTemplateData) o; + return SmartspaceUtils.isEqual(mHeadToHeadTitle, that.mHeadToHeadTitle) && Objects.equals( + mHeadToHeadFirstCompetitorIcon, that.mHeadToHeadFirstCompetitorIcon) + && Objects.equals( + mHeadToHeadSecondCompetitorIcon, that.mHeadToHeadSecondCompetitorIcon) + && SmartspaceUtils.isEqual(mHeadToHeadFirstCompetitorText, + that.mHeadToHeadFirstCompetitorText) + && SmartspaceUtils.isEqual(mHeadToHeadSecondCompetitorText, + that.mHeadToHeadSecondCompetitorText) + && Objects.equals( + mHeadToHeadAction, that.mHeadToHeadAction); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mHeadToHeadTitle, mHeadToHeadFirstCompetitorIcon, + mHeadToHeadSecondCompetitorIcon, mHeadToHeadFirstCompetitorText, + mHeadToHeadSecondCompetitorText, + mHeadToHeadAction); + } + + @Override + public String toString() { + return super.toString() + " + SmartspaceHeadToHeadUiTemplateData{" + + "mH2HTitle=" + mHeadToHeadTitle + + ", mH2HFirstCompetitorIcon=" + mHeadToHeadFirstCompetitorIcon + + ", mH2HSecondCompetitorIcon=" + mHeadToHeadSecondCompetitorIcon + + ", mH2HFirstCompetitorText=" + mHeadToHeadFirstCompetitorText + + ", mH2HSecondCompetitorText=" + mHeadToHeadSecondCompetitorText + + ", mH2HAction=" + mHeadToHeadAction + + '}'; + } + + /** + * A builder for {@link SmartspaceHeadToHeadUiTemplateData} object. + * + * @hide + */ + @SystemApi + public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder { + + private CharSequence mHeadToHeadTitle; + private SmartspaceIcon mHeadToHeadFirstCompetitorIcon; + private SmartspaceIcon mHeadToHeadSecondCompetitorIcon; + private CharSequence mHeadToHeadFirstCompetitorText; + private CharSequence mHeadToHeadSecondCompetitorText; + private SmartspaceTapAction mHeadToHeadAction; + + /** + * A builder for {@link SmartspaceHeadToHeadUiTemplateData}. + */ + public Builder() { + super(SmartspaceTarget.UI_TEMPLATE_HEAD_TO_HEAD); + } + + /** + * Sets the head-to-head card's title + */ + @NonNull + public Builder setHeadToHeadTitle(@Nullable CharSequence headToHeadTitle) { + mHeadToHeadTitle = headToHeadTitle; + return this; + } + + /** + * Sets the head-to-head card's first competitor icon + */ + @NonNull + public Builder setHeadToHeadFirstCompetitorIcon( + @Nullable SmartspaceIcon headToHeadFirstCompetitorIcon) { + mHeadToHeadFirstCompetitorIcon = headToHeadFirstCompetitorIcon; + return this; + } + + /** + * Sets the head-to-head card's second competitor icon + */ + @NonNull + public Builder setHeadToHeadSecondCompetitorIcon( + @Nullable SmartspaceIcon headToHeadSecondCompetitorIcon) { + mHeadToHeadSecondCompetitorIcon = headToHeadSecondCompetitorIcon; + return this; + } + + /** + * Sets the head-to-head card's first competitor text + */ + @NonNull + public Builder setHeadToHeadFirstCompetitorText( + @Nullable CharSequence headToHeadFirstCompetitorText) { + mHeadToHeadFirstCompetitorText = headToHeadFirstCompetitorText; + return this; + } + + /** + * Sets the head-to-head card's second competitor text + */ + @NonNull + public Builder setHeadToHeadSecondCompetitorText( + @Nullable CharSequence headToHeadSecondCompetitorText) { + mHeadToHeadSecondCompetitorText = headToHeadSecondCompetitorText; + return this; + } + + /** + * Sets the head-to-head card's tap action + */ + @NonNull + public Builder setHeadToHeadAction(@Nullable SmartspaceTapAction headToHeadAction) { + mHeadToHeadAction = headToHeadAction; + return this; + } + + /** + * Builds a new SmartspaceHeadToHeadUiTemplateData instance. + */ + @NonNull + public SmartspaceHeadToHeadUiTemplateData build() { + return new SmartspaceHeadToHeadUiTemplateData(getTemplateType(), getTitleText(), + getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(), + getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), + getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), + mHeadToHeadTitle, + mHeadToHeadFirstCompetitorIcon, + mHeadToHeadSecondCompetitorIcon, mHeadToHeadFirstCompetitorText, + mHeadToHeadSecondCompetitorText, + mHeadToHeadAction); + } + } +} diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java new file mode 100644 index 000000000000..70b30954afa7 --- /dev/null +++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceIcon.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.smartspace.uitemplatedata; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.smartspace.SmartspaceUtils; +import android.graphics.drawable.Icon; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * Holds the information for a Smartspace-card icon. Including the icon image itself, and an + * optional content description as the icon's accessibility description. + * + * @hide + */ +@SystemApi +public final class SmartspaceIcon implements Parcelable { + + @NonNull + private final Icon mIcon; + + @Nullable + private final CharSequence mContentDescription; + + SmartspaceIcon(@NonNull Parcel in) { + mIcon = in.readTypedObject(Icon.CREATOR); + mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + } + + private SmartspaceIcon(@NonNull Icon icon, @Nullable CharSequence contentDescription) { + mIcon = icon; + mContentDescription = contentDescription; + } + + @NonNull + public Icon getIcon() { + return mIcon; + } + + @Nullable + public CharSequence getContentDescription() { + return mContentDescription; + } + + @NonNull + public static final Creator<SmartspaceIcon> CREATOR = new Creator<SmartspaceIcon>() { + @Override + public SmartspaceIcon createFromParcel(Parcel in) { + return new SmartspaceIcon(in); + } + + @Override + public SmartspaceIcon[] newArray(int size) { + return new SmartspaceIcon[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SmartspaceIcon)) return false; + SmartspaceIcon that = (SmartspaceIcon) o; + return mIcon.equals(that.mIcon) && SmartspaceUtils.isEqual(mContentDescription, + that.mContentDescription); + } + + @Override + public int hashCode() { + return Objects.hash(mIcon, mContentDescription); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeTypedObject(mIcon, flags); + TextUtils.writeToParcel(mContentDescription, out, flags); + } + + @Override + public String toString() { + return "SmartspaceIcon{" + + "mImage=" + mIcon + + ", mContentDescription='" + mContentDescription + '\'' + + '}'; + } + + /** + * A builder for {@link SmartspaceIcon} object. + * + * @hide + */ + @SystemApi + public static final class Builder { + + private Icon mIcon; + private CharSequence mContentDescription; + + /** + * A builder for {@link SmartspaceIcon}. + * + * @param icon the icon image of this smartspace icon. + */ + public Builder(@NonNull Icon icon) { + mIcon = Objects.requireNonNull(icon); + } + + /** + * Sets the icon's content description. + */ + @NonNull + public Builder setContentDescription(@NonNull CharSequence contentDescription) { + mContentDescription = contentDescription; + return this; + } + + /** + * Builds a new SmartspaceIcon instance. + */ + @NonNull + public SmartspaceIcon build() { + return new SmartspaceIcon(mIcon, mContentDescription); + } + } +} diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java new file mode 100644 index 000000000000..287cf8e61bc3 --- /dev/null +++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubCardUiTemplateData.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.smartspace.uitemplatedata; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.smartspace.SmartspaceTarget; +import android.app.smartspace.SmartspaceUtils; +import android.os.Parcel; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * Holds all the relevant data needed to render a Smartspace card with the sub-card Ui Template. + * + * @hide + */ +@SystemApi +public final class SmartspaceSubCardUiTemplateData extends SmartspaceDefaultUiTemplateData { + + /** Icon for the sub-card. */ + @NonNull + private final SmartspaceIcon mSubCardIcon; + + /** Text for the sub-card, which shows below the icon when being set. */ + @Nullable + private final CharSequence mSubCardText; + + /** Tap action for the sub-card secondary card. */ + @Nullable + private final SmartspaceTapAction mSubCardAction; + + SmartspaceSubCardUiTemplateData(@NonNull Parcel in) { + super(in); + mSubCardIcon = in.readTypedObject(SmartspaceIcon.CREATOR); + mSubCardText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mSubCardAction = in.readTypedObject(SmartspaceTapAction.CREATOR); + } + + private SmartspaceSubCardUiTemplateData(int templateType, + @Nullable CharSequence titleText, + @Nullable SmartspaceIcon titleIcon, + @Nullable CharSequence subtitleText, + @Nullable SmartspaceIcon subTitleIcon, + @Nullable SmartspaceTapAction primaryTapAction, + @Nullable CharSequence supplementalSubtitleText, + @Nullable SmartspaceIcon supplementalSubtitleIcon, + @Nullable SmartspaceTapAction supplementalSubtitleTapAction, + @Nullable CharSequence supplementalAlarmText, + @NonNull SmartspaceIcon subCardIcon, + @Nullable CharSequence subCardText, + @Nullable SmartspaceTapAction subCardAction) { + super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, + supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, + supplementalAlarmText); + mSubCardIcon = subCardIcon; + mSubCardText = subCardText; + mSubCardAction = subCardAction; + } + + @NonNull + public SmartspaceIcon getSubCardIcon() { + return mSubCardIcon; + } + + @Nullable + public CharSequence getSubCardText() { + return mSubCardText; + } + + @Nullable + public SmartspaceTapAction getSubCardAction() { + return mSubCardAction; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<SmartspaceSubCardUiTemplateData> CREATOR = + new Creator<SmartspaceSubCardUiTemplateData>() { + @Override + public SmartspaceSubCardUiTemplateData createFromParcel(Parcel in) { + return new SmartspaceSubCardUiTemplateData(in); + } + + @Override + public SmartspaceSubCardUiTemplateData[] newArray(int size) { + return new SmartspaceSubCardUiTemplateData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeTypedObject(mSubCardIcon, flags); + TextUtils.writeToParcel(mSubCardText, out, flags); + out.writeTypedObject(mSubCardAction, flags); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SmartspaceSubCardUiTemplateData)) return false; + if (!super.equals(o)) return false; + SmartspaceSubCardUiTemplateData that = (SmartspaceSubCardUiTemplateData) o; + return mSubCardIcon.equals(that.mSubCardIcon) && SmartspaceUtils.isEqual(mSubCardText, + that.mSubCardText) && Objects.equals(mSubCardAction, + that.mSubCardAction); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mSubCardIcon, mSubCardText, mSubCardAction); + } + + @Override + public String toString() { + return super.toString() + " + SmartspaceSubCardUiTemplateData{" + + "mSubCardIcon=" + mSubCardIcon + + ", mSubCardText=" + mSubCardText + + ", mSubCardAction=" + mSubCardAction + + '}'; + } + + /** + * A builder for {@link SmartspaceSubCardUiTemplateData} object. + * + * @hide + */ + @SystemApi + public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder { + + private final SmartspaceIcon mSubCardIcon; + private CharSequence mSubCardText; + private SmartspaceTapAction mSubCardAction; + + /** + * A builder for {@link SmartspaceSubCardUiTemplateData}. + */ + public Builder(@NonNull SmartspaceIcon subCardIcon) { + super(SmartspaceTarget.UI_TEMPLATE_SUB_CARD); + mSubCardIcon = Objects.requireNonNull(subCardIcon); + } + + /** + * Sets the card title text. + */ + @NonNull + public Builder setSubCardAction(@NonNull CharSequence subCardTitleText) { + mSubCardText = subCardTitleText; + return this; + } + + /** + * Sets the card tap action. + */ + @NonNull + public Builder setSubCardAction(@NonNull SmartspaceTapAction subCardAction) { + mSubCardAction = subCardAction; + return this; + } + + /** + * Builds a new SmartspaceSubCardUiTemplateData instance. + */ + @NonNull + public SmartspaceSubCardUiTemplateData build() { + return new SmartspaceSubCardUiTemplateData(getTemplateType(), getTitleText(), + getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(), + getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), + getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubCardIcon, + mSubCardText, + mSubCardAction); + } + } +} diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java new file mode 100644 index 000000000000..c4799936f8c9 --- /dev/null +++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubImageUiTemplateData.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.smartspace.uitemplatedata; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.smartspace.SmartspaceTarget; +import android.os.Parcel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Holds all the relevant data needed to render a Smartspace card with the sub-image Ui Template. + * + * @hide + */ +@SystemApi +public final class SmartspaceSubImageUiTemplateData extends SmartspaceDefaultUiTemplateData { + + /** Texts are shown next to the image as a vertical list */ + @NonNull + private final List<CharSequence> mSubImageTexts; + + /** If multiple images are passed in, they will be rendered as GIF. */ + @NonNull + private final List<SmartspaceIcon> mSubImages; + + /** Tap action for the sub-image secondary card. */ + @Nullable + private final SmartspaceTapAction mSubImageAction; + + SmartspaceSubImageUiTemplateData(@NonNull Parcel in) { + super(in); + mSubImageTexts = Arrays.asList(in.readCharSequenceArray()); + mSubImages = in.createTypedArrayList(SmartspaceIcon.CREATOR); + mSubImageAction = in.readTypedObject(SmartspaceTapAction.CREATOR); + } + + private SmartspaceSubImageUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType, + @Nullable CharSequence titleText, + @Nullable SmartspaceIcon titleIcon, + @Nullable CharSequence subtitleText, + @Nullable SmartspaceIcon subTitleIcon, + @Nullable SmartspaceTapAction primaryTapAction, + @Nullable CharSequence supplementalSubtitleText, + @Nullable SmartspaceIcon supplementalSubtitleIcon, + @Nullable SmartspaceTapAction supplementalSubtitleTapAction, + @Nullable CharSequence supplementalAlarmText, + @NonNull List<CharSequence> subImageTexts, + @NonNull List<SmartspaceIcon> subImages, + @Nullable SmartspaceTapAction subImageAction) { + super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, + supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, + supplementalAlarmText); + mSubImageTexts = subImageTexts; + mSubImages = subImages; + mSubImageAction = subImageAction; + } + + @NonNull + public List<CharSequence> getSubImageTexts() { + return mSubImageTexts; + } + + @NonNull + public List<SmartspaceIcon> getSubImages() { + return mSubImages; + } + + @Nullable + public SmartspaceTapAction getSubImageAction() { + return mSubImageAction; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<SmartspaceSubImageUiTemplateData> CREATOR = + new Creator<SmartspaceSubImageUiTemplateData>() { + @Override + public SmartspaceSubImageUiTemplateData createFromParcel(Parcel in) { + return new SmartspaceSubImageUiTemplateData(in); + } + + @Override + public SmartspaceSubImageUiTemplateData[] newArray(int size) { + return new SmartspaceSubImageUiTemplateData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeCharSequenceList(new ArrayList<>(mSubImageTexts)); + out.writeTypedList(mSubImages); + out.writeTypedObject(mSubImageAction, flags); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SmartspaceSubImageUiTemplateData)) return false; + if (!super.equals(o)) return false; + SmartspaceSubImageUiTemplateData that = (SmartspaceSubImageUiTemplateData) o; + return Objects.equals(mSubImageTexts, that.mSubImageTexts) + && Objects.equals(mSubImages, that.mSubImages) && Objects.equals( + mSubImageAction, that.mSubImageAction); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mSubImageTexts, mSubImages, mSubImageAction); + } + + @Override + public String toString() { + return super.toString() + " + SmartspaceSubImageUiTemplateData{" + + "mSubImageTexts=" + mSubImageTexts + + ", mSubImages=" + mSubImages + + ", mSubImageAction=" + mSubImageAction + + '}'; + } + + /** + * A builder for {@link SmartspaceSubImageUiTemplateData} object. + * + * @hide + */ + @SystemApi + public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder { + + private final List<CharSequence> mSubImageTexts; + private final List<SmartspaceIcon> mSubImages; + private SmartspaceTapAction mSubImageAction; + + /** + * A builder for {@link SmartspaceSubImageUiTemplateData}. + */ + public Builder(@NonNull List<CharSequence> subImageTexts, + @NonNull List<SmartspaceIcon> subImages) { + super(SmartspaceTarget.UI_TEMPLATE_SUB_IMAGE); + mSubImageTexts = Objects.requireNonNull(subImageTexts); + mSubImages = Objects.requireNonNull(subImages); + } + + /** + * Sets the card tap action. + */ + @NonNull + public Builder setCarouselAction(@NonNull SmartspaceTapAction subImageAction) { + mSubImageAction = subImageAction; + return this; + } + + /** + * Builds a new SmartspaceSubImageUiTemplateData instance. + */ + @NonNull + public SmartspaceSubImageUiTemplateData build() { + return new SmartspaceSubImageUiTemplateData(getTemplateType(), getTitleText(), + getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(), + getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), + getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubImageTexts, + mSubImages, + mSubImageAction); + } + } +} diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java new file mode 100644 index 000000000000..b5d9645027d8 --- /dev/null +++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceSubListUiTemplateData.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.smartspace.uitemplatedata; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.smartspace.SmartspaceTarget; +import android.os.Parcel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Holds all the relevant data needed to render a Smartspace card with the sub-list Ui Template. + * + * @hide + */ +@SystemApi +public final class SmartspaceSubListUiTemplateData extends SmartspaceDefaultUiTemplateData { + + @Nullable + private final SmartspaceIcon mSubListIcon; + @NonNull + private final List<CharSequence> mSubListTexts; + + /** Tap action for the sub-list secondary card. */ + @Nullable + private final SmartspaceTapAction mSubListAction; + + SmartspaceSubListUiTemplateData(@NonNull Parcel in) { + super(in); + mSubListIcon = in.readTypedObject(SmartspaceIcon.CREATOR); + mSubListTexts = Arrays.asList(in.readCharSequenceArray()); + mSubListAction = in.readTypedObject(SmartspaceTapAction.CREATOR); + } + + private SmartspaceSubListUiTemplateData(@SmartspaceTarget.UiTemplateType int templateType, + @Nullable CharSequence titleText, + @Nullable SmartspaceIcon titleIcon, + @Nullable CharSequence subtitleText, + @Nullable SmartspaceIcon subTitleIcon, + @Nullable SmartspaceTapAction primaryTapAction, + @Nullable CharSequence supplementalSubtitleText, + @Nullable SmartspaceIcon supplementalSubtitleIcon, + @Nullable SmartspaceTapAction supplementalSubtitleTapAction, + @Nullable CharSequence supplementalAlarmText, + @Nullable SmartspaceIcon subListIcon, + @NonNull List<CharSequence> subListTexts, + @Nullable SmartspaceTapAction subListAction) { + super(templateType, titleText, titleIcon, subtitleText, subTitleIcon, primaryTapAction, + supplementalSubtitleText, supplementalSubtitleIcon, supplementalSubtitleTapAction, + supplementalAlarmText); + mSubListIcon = subListIcon; + mSubListTexts = subListTexts; + mSubListAction = subListAction; + } + + @Nullable + public SmartspaceIcon getSubListIcon() { + return mSubListIcon; + } + + @NonNull + public List<CharSequence> getSubListTexts() { + return mSubListTexts; + } + + @Nullable + public SmartspaceTapAction getSubListAction() { + return mSubListAction; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<SmartspaceSubListUiTemplateData> CREATOR = + new Creator<SmartspaceSubListUiTemplateData>() { + @Override + public SmartspaceSubListUiTemplateData createFromParcel(Parcel in) { + return new SmartspaceSubListUiTemplateData(in); + } + + @Override + public SmartspaceSubListUiTemplateData[] newArray(int size) { + return new SmartspaceSubListUiTemplateData[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeTypedObject(mSubListIcon, flags); + out.writeCharSequenceList(new ArrayList<>(mSubListTexts)); + out.writeTypedObject(mSubListAction, flags); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SmartspaceSubListUiTemplateData)) return false; + if (!super.equals(o)) return false; + SmartspaceSubListUiTemplateData that = (SmartspaceSubListUiTemplateData) o; + return Objects.equals(mSubListIcon, that.mSubListIcon) && Objects.equals( + mSubListTexts, that.mSubListTexts) && Objects.equals(mSubListAction, + that.mSubListAction); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), mSubListIcon, mSubListTexts, mSubListAction); + } + + @Override + public String toString() { + return super.toString() + " + SmartspaceSubListUiTemplateData{" + + "mSubListIcon=" + mSubListIcon + + ", mSubListTexts=" + mSubListTexts + + ", mSubListAction=" + mSubListAction + + '}'; + } + + /** + * A builder for {@link SmartspaceSubListUiTemplateData} object. + * + * @hide + */ + @SystemApi + public static final class Builder extends SmartspaceDefaultUiTemplateData.Builder { + + private SmartspaceIcon mSubListIcon; + private final List<CharSequence> mSubListTexts; + private SmartspaceTapAction mSubListAction; + + /** + * A builder for {@link SmartspaceSubListUiTemplateData}. + */ + public Builder(@NonNull List<CharSequence> subListTexts) { + super(SmartspaceTarget.UI_TEMPLATE_SUB_LIST); + mSubListTexts = Objects.requireNonNull(subListTexts); + } + + /** + * Sets the sub-list card icon. + */ + @NonNull + public Builder setSubListIcon(@NonNull SmartspaceIcon subListIcon) { + mSubListIcon = subListIcon; + return this; + } + + /** + * Sets the card tap action. + */ + @NonNull + public Builder setCarouselAction(@NonNull SmartspaceTapAction subListAction) { + mSubListAction = subListAction; + return this; + } + + /** + * Builds a new SmartspaceSubListUiTemplateData instance. + */ + @NonNull + public SmartspaceSubListUiTemplateData build() { + return new SmartspaceSubListUiTemplateData(getTemplateType(), getTitleText(), + getTitleIcon(), getSubtitleText(), getSubTitleIcon(), getPrimaryTapAction(), + getSupplementalSubtitleText(), getSupplementalSubtitleIcon(), + getSupplementalSubtitleTapAction(), getSupplementalAlarmText(), mSubListIcon, + mSubListTexts, + mSubListAction); + } + } +} diff --git a/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java b/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java new file mode 100644 index 000000000000..27d8e5fed3b1 --- /dev/null +++ b/core/java/android/app/smartspace/uitemplatedata/SmartspaceTapAction.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.smartspace.uitemplatedata; + + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.app.PendingIntent; +import android.app.smartspace.SmartspaceUtils; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.UserHandle; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * A {@link SmartspaceTapAction} represents an action which can be taken by a user by tapping on + * either the title, the subtitle or on the icon. Supported instances are Intents and + * PendingIntents. These actions can be called from another process or within the client process. + * + * Clients can also receive ShorcutInfos in the extras bundle. + * + * @hide + */ +@SystemApi +public final class SmartspaceTapAction implements Parcelable { + + /** A unique Id of this {@link SmartspaceTapAction}. */ + @Nullable + private final CharSequence mId; + + @Nullable + private final Intent mIntent; + + @Nullable + private final PendingIntent mPendingIntent; + + @Nullable + private final UserHandle mUserHandle; + + @Nullable + private Bundle mExtras; + + SmartspaceTapAction(@NonNull Parcel in) { + mId = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mIntent = in.readTypedObject(Intent.CREATOR); + mPendingIntent = in.readTypedObject(PendingIntent.CREATOR); + mUserHandle = in.readTypedObject(UserHandle.CREATOR); + mExtras = in.readBundle(); + } + + private SmartspaceTapAction(@Nullable CharSequence id, @Nullable Intent intent, + @Nullable PendingIntent pendingIntent, @Nullable UserHandle userHandle, + @Nullable Bundle extras) { + mId = id; + mIntent = intent; + mPendingIntent = pendingIntent; + mUserHandle = userHandle; + mExtras = extras; + } + + @Nullable + public CharSequence getId() { + return mId; + } + + @SuppressLint("IntentBuilderName") + @Nullable + public Intent getIntent() { + return mIntent; + } + + @Nullable + public PendingIntent getPendingIntent() { + return mPendingIntent; + } + + @Nullable + public UserHandle getUserHandle() { + return mUserHandle; + } + + @Nullable + @SuppressLint("NullableCollection") + public Bundle getExtras() { + return mExtras; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + TextUtils.writeToParcel(mId, out, flags); + out.writeTypedObject(mIntent, flags); + out.writeTypedObject(mPendingIntent, flags); + out.writeTypedObject(mUserHandle, flags); + out.writeBundle(mExtras); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator<SmartspaceTapAction> CREATOR = new Creator<SmartspaceTapAction>() { + @Override + public SmartspaceTapAction createFromParcel(Parcel in) { + return new SmartspaceTapAction(in); + } + + @Override + public SmartspaceTapAction[] newArray(int size) { + return new SmartspaceTapAction[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SmartspaceTapAction)) return false; + SmartspaceTapAction that = (SmartspaceTapAction) o; + return SmartspaceUtils.isEqual(mId, that.mId); + } + + @Override + public int hashCode() { + return Objects.hash(mId); + } + + @Override + public String toString() { + return "SmartspaceTapAction{" + + "mId=" + mId + + "mIntent=" + mIntent + + ", mPendingIntent=" + mPendingIntent + + ", mUserHandle=" + mUserHandle + + ", mExtras=" + mExtras + + '}'; + } + + /** + * A builder for {@link SmartspaceTapAction} object. + * + * @hide + */ + @SystemApi + public static final class Builder { + + private CharSequence mId; + private Intent mIntent; + private PendingIntent mPendingIntent; + private UserHandle mUserHandle; + private Bundle mExtras; + + /** + * A builder for {@link SmartspaceTapAction}. + * + * @param id A unique Id of this {@link SmartspaceTapAction}. + */ + public Builder(@NonNull CharSequence id) { + mId = Objects.requireNonNull(id); + } + + /** + * Sets the action intent. + */ + @NonNull + public Builder setIntent(@NonNull Intent intent) { + mIntent = intent; + return this; + } + + /** + * Sets the pending intent. + */ + @NonNull + public Builder setPendingIntent(@NonNull PendingIntent pendingIntent) { + mPendingIntent = pendingIntent; + return this; + } + + /** + * Sets the user handle. + */ + @NonNull + @SuppressLint("UserHandleName") + public Builder setUserHandle(@Nullable UserHandle userHandle) { + mUserHandle = userHandle; + return this; + } + + /** + * Sets the extras. + */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Builds a new SmartspaceTapAction instance. + * + * @throws IllegalStateException if the tap action is empty. + */ + @NonNull + public SmartspaceTapAction build() { + if (mIntent == null && mPendingIntent == null && mExtras == null) { + throw new IllegalStateException("Please assign at least 1 valid tap field"); + } + return new SmartspaceTapAction(mId, mIntent, mPendingIntent, mUserHandle, mExtras); + } + } +} diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index edabccf23c2c..7956a35c7b3d 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -17,6 +17,7 @@ package android.app.trust; import android.app.trust.ITrustListener; +import android.content.ComponentName; import android.hardware.biometrics.BiometricSourceType; /** @@ -29,6 +30,7 @@ interface ITrustManager { void reportUserRequestedUnlock(int userId); void reportUnlockLockout(int timeoutMs, int userId); void reportEnabledTrustAgentsChanged(int userId); + void enableTrustAgentForUserForTest(in ComponentName componentName, int userId); void registerTrustListener(in ITrustListener trustListener); void unregisterTrustListener(in ITrustListener trustListener); void reportKeyguardShowingChanged(); diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 70b7de0767e4..fba2d3e03769 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -16,10 +16,14 @@ package android.app.trust; -import android.Manifest; +import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; + +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.Context; import android.hardware.biometrics.BiometricSourceType; import android.os.Handler; @@ -33,9 +37,17 @@ import java.util.ArrayList; import java.util.List; /** - * See {@link com.android.server.trust.TrustManagerService} + * Interface to the system service managing trust. + * + * <p>This class is for internal use only. This class is marked {@code @TestApi} to + * enable testing the trust system including {@link android.service.trust.TrustAgentService}. + * Methods which are currently not used in tests are marked @hide. + * + * @see com.android.server.trust.TrustManagerService + * * @hide */ +@TestApi @SystemService(Context.TRUST_SERVICE) public class TrustManager { @@ -51,7 +63,8 @@ public class TrustManager { private final ITrustManager mService; private final ArrayMap<TrustListener, ITrustListener> mTrustListeners; - public TrustManager(IBinder b) { + /** @hide */ + public TrustManager(@NonNull IBinder b) { mService = ITrustManager.Stub.asInterface(b); mTrustListeners = new ArrayMap<TrustListener, ITrustListener>(); } @@ -62,8 +75,10 @@ public class TrustManager { * * @param userId The id for the user to be locked/unlocked. * @param locked The value for that user's locked state. + * + * @hide */ - @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) + @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE) public void setDeviceLockedForUser(int userId, boolean locked) { try { mService.setDeviceLockedForUser(userId, locked); @@ -78,8 +93,11 @@ public class TrustManager { * @param successful if true, the unlock attempt was successful. * * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + * + * @hide */ @UnsupportedAppUsage + @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE) public void reportUnlockAttempt(boolean successful, int userId) { try { mService.reportUnlockAttempt(successful, userId); @@ -93,6 +111,7 @@ public class TrustManager { * * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. */ + @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE) public void reportUserRequestedUnlock(int userId) { try { mService.reportUserRequestedUnlock(userId); @@ -112,7 +131,10 @@ public class TrustManager { * attempt to unlock the device again. * * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + * + * @hide */ + @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE) public void reportUnlockLockout(int timeoutMs, int userId) { try { mService.reportUnlockLockout(timeoutMs, userId); @@ -125,7 +147,10 @@ public class TrustManager { * Reports that the list of enabled trust agents changed for user {@param userId}. * * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + * + * @hide */ + @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE) public void reportEnabledTrustAgentsChanged(int userId) { try { mService.reportEnabledTrustAgentsChanged(userId); @@ -135,10 +160,33 @@ public class TrustManager { } /** + * Enables a trust agent. + * + * <p>The agent is specified by {@code componentName} and must be a subclass of + * {@link android.service.trust.TrustAgentService} and otherwise meet the requirements + * to be a trust agent. + * + * <p>This method can only be used in tests. + * + * @param componentName the trust agent to enable + */ + @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE) + public void enableTrustAgentForUserForTest(@NonNull ComponentName componentName, int userId) { + try { + mService.enableTrustAgentForUserForTest(componentName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Reports that the visibility of the keyguard has changed. * * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + * + * @hide */ + @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE) public void reportKeyguardShowingChanged() { try { mService.reportKeyguardShowingChanged(); @@ -151,7 +199,10 @@ public class TrustManager { * Registers a listener for trust events. * * Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission. + * + * @hide */ + @RequiresPermission(android.Manifest.permission.TRUST_LISTENER) public void registerTrustListener(final TrustListener trustListener) { try { ITrustListener.Stub iTrustListener = new ITrustListener.Stub() { @@ -192,7 +243,10 @@ public class TrustManager { * Unregisters a listener for trust events. * * Requires the {@link android.Manifest.permission#TRUST_LISTENER} permission. + * + * @hide */ + @RequiresPermission(android.Manifest.permission.TRUST_LISTENER) public void unregisterTrustListener(final TrustListener trustListener) { ITrustListener iTrustListener = mTrustListeners.remove(trustListener); if (iTrustListener != null) { @@ -207,6 +261,8 @@ public class TrustManager { /** * @return whether {@param userId} has enabled and configured trust agents. Ignores short-term * unavailability of trust due to {@link LockPatternUtils.StrongAuthTracker}. + * + * @hide */ @RequiresPermission(android.Manifest.permission.TRUST_LISTENER) public boolean isTrustUsuallyManaged(int userId) { @@ -223,8 +279,10 @@ public class TrustManager { * can be skipped. * * @param userId + * + * @hide */ - @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) + @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE) public void unlockedByBiometricForUser(int userId, BiometricSourceType source) { try { mService.unlockedByBiometricForUser(userId, source); @@ -235,8 +293,10 @@ public class TrustManager { /** * Clears authentication by the specified biometric type for all users. + * + * @hide */ - @RequiresPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE) + @RequiresPermission(ACCESS_KEYGUARD_SECURE_STORAGE) public void clearAllBiometricRecognized(BiometricSourceType source, int unlockedUser) { try { mService.clearAllBiometricRecognized(source, unlockedUser); @@ -264,6 +324,7 @@ public class TrustManager { } }; + /** @hide */ public interface TrustListener { /** diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java index a36da8816d60..6e3bbccdbef0 100644 --- a/core/java/android/apphibernation/AppHibernationManager.java +++ b/core/java/android/apphibernation/AppHibernationManager.java @@ -24,7 +24,10 @@ import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; /** * This class provides an API surface for system apps to manipulate the app hibernation @@ -129,4 +132,38 @@ public class AppHibernationManager { throw e.rethrowFromSystemServer(); } } + + /** + * Returns the stats from app hibernation for each package provided. + * + * @param packageNames the set of packages to return stats for + * @hide + */ + @SystemApi + @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION) + public @NonNull Map<String, HibernationStats> getHibernationStatsForUser( + @NonNull Set<String> packageNames) { + try { + return mIAppHibernationService.getHibernationStatsForUser( + new ArrayList(packageNames), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the stats from app hibernation for all packages for the user + * + * @hide + */ + @SystemApi + @RequiresPermission(value = android.Manifest.permission.MANAGE_APP_HIBERNATION) + public @NonNull Map<String, HibernationStats> getHibernationStatsForUser() { + try { + return mIAppHibernationService.getHibernationStatsForUser( + null /* packageNames */, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/apphibernation/HibernationStats.aidl b/core/java/android/apphibernation/HibernationStats.aidl new file mode 100644 index 000000000000..a92b903f56dc --- /dev/null +++ b/core/java/android/apphibernation/HibernationStats.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.apphibernation; + +parcelable HibernationStats;
\ No newline at end of file diff --git a/core/java/android/apphibernation/HibernationStats.java b/core/java/android/apphibernation/HibernationStats.java new file mode 100644 index 000000000000..2c4db8218643 --- /dev/null +++ b/core/java/android/apphibernation/HibernationStats.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.apphibernation; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Stats for a hibernating package. + * @hide + */ +@SystemApi +public final class HibernationStats implements Parcelable { + private final long mDiskBytesSaved; + + /** @hide */ + public HibernationStats(long diskBytesSaved) { + mDiskBytesSaved = diskBytesSaved; + } + + private HibernationStats(@NonNull Parcel in) { + mDiskBytesSaved = in.readLong(); + } + + /** + * Get the disk storage saved from hibernation in bytes. + */ + public long getDiskBytesSaved() { + return mDiskBytesSaved; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mDiskBytesSaved); + } + + public static final @NonNull Creator<HibernationStats> CREATOR = + new Creator<HibernationStats>() { + @Override + public HibernationStats createFromParcel(Parcel in) { + return new HibernationStats(in); + } + + @Override + public HibernationStats[] newArray(int size) { + return new HibernationStats[size]; + } + }; +} diff --git a/core/java/android/apphibernation/IAppHibernationService.aidl b/core/java/android/apphibernation/IAppHibernationService.aidl index afdb3fe03dad..11bb6b505cdf 100644 --- a/core/java/android/apphibernation/IAppHibernationService.aidl +++ b/core/java/android/apphibernation/IAppHibernationService.aidl @@ -16,6 +16,8 @@ package android.apphibernation; +import android.apphibernation.HibernationStats; + /** * Binder interface to communicate with AppHibernationService. * @hide @@ -26,4 +28,6 @@ interface IAppHibernationService { boolean isHibernatingGlobally(String packageName); void setHibernatingGlobally(String packageName, boolean isHibernating); List<String> getHibernatingPackagesForUser(int userId); + Map<String, HibernationStats> getHibernationStatsForUser(in List<String> packageNames, + int userId); }
\ No newline at end of file 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..0b8a8a23aee4 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -4987,10 +4987,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 diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index a5d97f958ea5..bb88486a014d 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -363,6 +363,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { /** * The group this permission is a part of, as per * {@link android.R.attr#permissionGroup}. + * <p> + * The actual grouping of platform-defined runtime permissions is subject to change and can be + * queried with {@link PackageManager#getGroupOfPlatformPermission}. */ public @Nullable String group; 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/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/startop/scripts/trace_analyzer/queries_pretty_print_block_launch.sql b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl index bf5e3ccb4ac7..55cab52fc4f7 100644 --- a/startop/scripts/trace_analyzer/queries_pretty_print_block_launch.sql +++ b/core/java/android/hardware/biometrics/IBiometricContextListener.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package android.hardware.biometrics; -.headers on -.mode quote - -SELECT * FROM blocked_iowait_for_app_launches; - -/* -Output as CSV example: - -'blocked_iowait_duration_ms','process_name','launching_duration_ms','launching_started_timestamp_ms','launching_finished_timestamp_ms' -125.33199995596078224,'com.android.settings',1022.4840000009862706,17149896.822000000626,17150919.305999998003 - -*/ +/** + * 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/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java index 0dc8f92967fb..99f3d156cfa9 100644 --- a/core/java/android/hardware/display/BrightnessInfo.java +++ b/core/java/android/hardware/display/BrightnessInfo.java @@ -57,6 +57,23 @@ public final class BrightnessInfo implements Parcelable { */ public static final int HIGH_BRIGHTNESS_MODE_HDR = 2; + @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = { + BRIGHTNESS_MAX_REASON_NONE, + BRIGHTNESS_MAX_REASON_THERMAL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BrightnessMaxReason {} + + /** + * Maximum brightness is unrestricted. + */ + public static final int BRIGHTNESS_MAX_REASON_NONE = 0; + + /** + * Maximum brightness is restricted due to thermal throttling. + */ + public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1; + /** Brightness */ public final float brightness; @@ -78,21 +95,29 @@ public final class BrightnessInfo implements Parcelable { */ public final int highBrightnessMode; + /** + * The current reason for restricting maximum brightness. + * Can be any of BRIGHTNESS_MAX_REASON_* values. + */ + public final int brightnessMaxReason; + public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum, - @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint) { + @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint, + @BrightnessMaxReason int brightnessMaxReason) { this(brightness, brightness, brightnessMinimum, brightnessMaximum, highBrightnessMode, - highBrightnessTransitionPoint); + highBrightnessTransitionPoint, brightnessMaxReason); } public BrightnessInfo(float brightness, float adjustedBrightness, float brightnessMinimum, float brightnessMaximum, @HighBrightnessMode int highBrightnessMode, - float highBrightnessTransitionPoint) { + float highBrightnessTransitionPoint, @BrightnessMaxReason int brightnessMaxReason) { this.brightness = brightness; this.adjustedBrightness = adjustedBrightness; this.brightnessMinimum = brightnessMinimum; this.brightnessMaximum = brightnessMaximum; this.highBrightnessMode = highBrightnessMode; this.highBrightnessTransitionPoint = highBrightnessTransitionPoint; + this.brightnessMaxReason = brightnessMaxReason; } /** @@ -110,6 +135,19 @@ public final class BrightnessInfo implements Parcelable { return "invalid"; } + /** + * @return User-friendly string for specified {@link BrightnessMaxReason} parameter. + */ + public static String briMaxReasonToString(@BrightnessMaxReason int reason) { + switch (reason) { + case BRIGHTNESS_MAX_REASON_NONE: + return "none"; + case BRIGHTNESS_MAX_REASON_THERMAL: + return "thermal"; + } + return "invalid"; + } + @Override public int describeContents() { return 0; @@ -123,6 +161,7 @@ public final class BrightnessInfo implements Parcelable { dest.writeFloat(brightnessMaximum); dest.writeInt(highBrightnessMode); dest.writeFloat(highBrightnessTransitionPoint); + dest.writeInt(brightnessMaxReason); } public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR = @@ -145,6 +184,7 @@ public final class BrightnessInfo implements Parcelable { brightnessMaximum = source.readFloat(); highBrightnessMode = source.readInt(); highBrightnessTransitionPoint = source.readFloat(); + brightnessMaxReason = source.readInt(); } } 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..f55c41594389 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -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; diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index 2484cf079c56..508172d13aa3 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; @@ -44,6 +45,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; @@ -71,6 +74,10 @@ final class NavigationBarController { default void onDestroy() { } + default void setShouldShowImeSwitcherWhenImeIsShown( + boolean shouldShowImeSwitcherWhenImeIsShown) { + } + default void onSystemBarAppearanceChanged(@Appearance int appearance) { } @@ -106,6 +113,10 @@ final class NavigationBarController { mImpl.onDestroy(); } + void setShouldShowImeSwitcherWhenImeIsShown(boolean shouldShowImeSwitcherWhenImeIsShown) { + mImpl.setShouldShowImeSwitcherWhenImeIsShown(shouldShowImeSwitcherWhenImeIsShown); + } + void onSystemBarAppearanceChanged(@Appearance int appearance) { mImpl.onSystemBarAppearanceChanged(appearance); } @@ -115,6 +126,12 @@ final class NavigationBarController { } private static final class Impl implements Callback { + 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 +147,17 @@ 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; + Impl(@NonNull InputMethodService inputMethodService) { mService = inputMethodService; } @@ -190,7 +215,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 { @@ -368,6 +395,10 @@ final class NavigationBarController { if (mDestroyed) { return; } + if (mTintAnimator != null) { + mTintAnimator.cancel(); + mTintAnimator = null; + } if (mSystemOverlayChangedReceiver != null) { mService.unregisterReceiver(mSystemOverlayChangedReceiver); mSystemOverlayChangedReceiver = null; @@ -404,6 +435,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; @@ -416,10 +472,24 @@ final class NavigationBarController { } final float targetDarkIntensity = calculateTargetDarkIntensity(mAppearance); - setIconTintInternal(targetDarkIntensity); + + 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) { @@ -438,7 +508,9 @@ final class NavigationBarController { public String toDebugString() { return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons + " mNavigationBarFrame=" + mNavigationBarFrame + + " mShouldShowImeSwitcherWhenImeIsShown" + mShouldShowImeSwitcherWhenImeIsShown + " mAppearance=0x" + Integer.toHexString(mAppearance) + + " mDarkIntensity=" + mDarkIntensity + "}"; } } 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 425e797a9e0e..df5b7bc237df 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -65,6 +65,11 @@ interface IPowerManager boolean isBatteryDischargePredictionPersonalized(); boolean isDeviceIdleMode(); boolean isLightDeviceIdleMode(); + boolean isLowPowerStandbySupported(); + boolean isLowPowerStandbyEnabled(); + void setLowPowerStandbyEnabled(boolean enabled); + void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance); + void forceLowPowerStandbyActive(boolean active); @UnsupportedAppUsage void reboot(boolean confirm, String reason, boolean wait); diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 881fcedea6f7..5bd8588d8970 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -216,6 +216,17 @@ public final class PowerManager { public static final int UNIMPORTANT_FOR_LOGGING = 0x40000000; /** + * Wake lock flag: This wake lock should be held by the system. + * + * <p>Meant to allow tests to keep the device awake even when power restrictions are active. + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.DEVICE_POWER) + public static final int SYSTEM_WAKELOCK = 0x80000000; + + /** * Flag for {@link WakeLock#release WakeLock.release(int)}: Defer releasing a * {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor * indicates that an object is not in close proximity. @@ -2146,6 +2157,105 @@ public final class PowerManager { } /** + * Returns true if Low Power Standby is supported on this device. + * + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public boolean isLowPowerStandbySupported() { + try { + return mService.isLowPowerStandbySupported(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns true if Low Power Standby is enabled. + * + * <p>When Low Power Standby is enabled, apps (including apps running foreground services) are + * subject to additional restrictions while the device is non-interactive, outside of device + * idle maintenance windows: Their network access is disabled, and any wakelocks they hold are + * ignored. + * + * <p>When Low Power Standby is enabled or disabled, a Intent with action + * {@link #ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED} is broadcast to registered receivers. + */ + public boolean isLowPowerStandbyEnabled() { + try { + return mService.isLowPowerStandbyEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set whether Low Power Standby is enabled. + * Does nothing if Low Power Standby is not supported. + * + * @see #isLowPowerStandbySupported() + * @see #isLowPowerStandbyEnabled() + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public void setLowPowerStandbyEnabled(boolean enabled) { + try { + mService.setLowPowerStandbyEnabled(enabled); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set whether Low Power Standby should be active during doze maintenance mode. + * Does nothing if Low Power Standby is not supported. + * + * @see #isLowPowerStandbySupported() + * @see #isLowPowerStandbyEnabled() + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance) { + try { + mService.setLowPowerStandbyActiveDuringMaintenance(activeDuringMaintenance); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Force Low Power Standby restrictions to be active. + * Does nothing if Low Power Standby is not supported. + * + * @see #isLowPowerStandbySupported() + * @hide + */ + @TestApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public void forceLowPowerStandbyActive(boolean active) { + try { + mService.forceLowPowerStandbyActive(active); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Return whether the given application package name is on the device's power allowlist. * Apps can be placed on the allowlist through the settings UI invoked by * {@link android.provider.Settings#ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS}. @@ -2631,6 +2741,16 @@ public final class PowerManager { = "android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED"; /** + * Intent that is broadcast when Low Power Standby is enabled or disabled. + * This broadcast is only sent to registered receivers. + * + * @see #isLowPowerStandbyEnabled() + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED = + "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED"; + + /** * Constant for PreIdleTimeout normal mode (default mode, not short nor extend timeout) . * @hide */ diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java index eb18b96e255b..ec4d3b6a2441 100644 --- a/core/java/android/os/PowerManagerInternal.java +++ b/core/java/android/os/PowerManagerInternal.java @@ -184,6 +184,21 @@ public abstract class PowerManagerInternal { public abstract void setDeviceIdleTempWhitelist(int[] appids); + /** + * Updates the Low Power Standby allowlist. + * + * @param uids UIDs that are exempt from Low Power Standby restrictions + */ + public abstract void setLowPowerStandbyAllowlist(int[] uids); + + /** + * Used by LowPowerStandbyController to notify the power manager that Low Power Standby's + * active state has changed. + * + * @param active {@code true} to activate Low Power Standby, {@code false} to turn it off. + */ + public abstract void setLowPowerStandbyActive(boolean active); + public abstract void startUidChanges(); public abstract void finishUidChanges(); diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 8bc219b7dc57..d223a19b4348 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -37,6 +37,7 @@ public final class VibrationAttributes implements Parcelable { USAGE_CLASS_UNKNOWN, USAGE_CLASS_ALARM, USAGE_CLASS_FEEDBACK, + USAGE_CLASS_MEDIA, }) @Retention(RetentionPolicy.SOURCE) public @interface UsageClass {} @@ -105,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; /** @@ -182,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() { @@ -191,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() { @@ -428,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) { @@ -459,4 +458,3 @@ public final class VibrationAttributes implements Parcelable { } } } - diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index f490587ae1e1..9598410d8f33 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. */ 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/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 8df659de5924..63616da25982 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -291,6 +291,8 @@ public class StorageManager { public static final int FLAG_INCLUDE_INVISIBLE = 1 << 10; /** {@hide} */ public static final int FLAG_INCLUDE_RECENT = 1 << 11; + /** {@hide} */ + public static final int FLAG_INCLUDE_SHARED_PROFILE = 1 << 12; /** {@hide} */ public static final int FSTRIM_FLAG_DEEP = IVold.FSTRIM_FLAG_DEEP_TRIM; @@ -1328,6 +1330,23 @@ public class StorageManager { } /** + * Return the list of shared/external storage volumes currently available to + * the calling user and the user it shares media with + * CDD link : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support + * <p> + * This is similar to {@link StorageManager#getStorageVolumes()} except that the result also + * includes the volumes belonging to any user it shares media with + */ + @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) + public @NonNull List<StorageVolume> getStorageVolumesIncludingSharedProfiles() { + final ArrayList<StorageVolume> res = new ArrayList<>(); + Collections.addAll(res, + getVolumeList(mContext.getUserId(), + FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_SHARED_PROFILE)); + return res; + } + + /** * Return the list of shared/external storage volumes both currently and * recently available to the calling user. * <p> diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 8ee52c21e869..e1f112af41e7 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -16,8 +16,6 @@ package android.os.storage; -import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -308,11 +306,9 @@ public final class StorageVolume implements Parcelable { /** * Returns the user that owns this volume - * - * {@hide} */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - @SystemApi(client = MODULE_LIBRARIES) + // TODO(b/193460475) : Android Lint handle API change from systemApi to public Api incorrectly + @SuppressLint("NewApi") public @NonNull UserHandle getOwner() { return mOwner; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2a563ac47294..14055ac19e5b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2386,6 +2386,15 @@ public final class Settings { "android.settings.ENABLE_MMS_DATA_REQUEST"; /** + * Shows restrict settings dialog when settings is blocked. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SHOW_RESTRICTED_SETTING_DIALOG = + "android.settings.SHOW_RESTRICTED_SETTING_DIALOG"; + + /** * Integer value that specifies the reason triggering enable MMS data notification. * This must be passed as an extra field to the {@link #ACTION_ENABLE_MMS_DATA_REQUEST}. * Extra with value of EnableMmsDataReason interface. @@ -6574,6 +6583,8 @@ public final class Settings { * @hide */ @Readable(maxTargetSdk = Build.VERSION_CODES.S) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLUETOOTH_NAME = "bluetooth_name"; /** @@ -6581,6 +6592,8 @@ public final class Settings { * @hide */ @Readable(maxTargetSdk = Build.VERSION_CODES.S) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLUETOOTH_ADDRESS = "bluetooth_address"; /** @@ -6588,6 +6601,8 @@ public final class Settings { * @hide */ @Readable(maxTargetSdk = Build.VERSION_CODES.S) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLUETOOTH_ADDR_VALID = "bluetooth_addr_valid"; /** @@ -10259,6 +10274,13 @@ public final class Settings { public static final String NEARBY_SHARING_COMPONENT = "nearby_sharing_component"; /** + * Nearby Sharing Slice URI for the SliceProvider to + * read Nearby Sharing scan results and then draw the UI. + * @hide + */ + public static final String NEARBY_SHARING_SLICE_URI = "nearby_sharing_slice_uri"; + + /** * Controls whether aware is enabled. * @hide */ @@ -10826,6 +10848,8 @@ public final class Settings { * @hide */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device"; /** @@ -10834,6 +10858,8 @@ public final class Settings { * {@hide} */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLUETOOTH_DISABLED_PROFILES = "bluetooth_disabled_profiles"; /** @@ -12371,6 +12397,8 @@ public final class Settings { * @hide */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled"; /** @@ -12378,6 +12406,8 @@ public final class Settings { * @hide */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms"; /** @@ -12385,6 +12415,8 @@ public final class Settings { * @hide */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms"; /** @@ -12392,6 +12424,8 @@ public final class Settings { * @hide */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS = "ble_scan_low_latency_window_ms"; @@ -12400,6 +12434,8 @@ public final class Settings { * @hide */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS = "ble_scan_low_power_interval_ms"; @@ -12408,6 +12444,8 @@ public final class Settings { * @hide */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLE_SCAN_BALANCED_INTERVAL_MS = "ble_scan_balanced_interval_ms"; @@ -12416,6 +12454,8 @@ public final class Settings { * @hide */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS = "ble_scan_low_latency_interval_ms"; @@ -12424,6 +12464,8 @@ public final class Settings { * @hide */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLE_SCAN_BACKGROUND_MODE = "ble_scan_background_mode"; /** @@ -13156,6 +13198,8 @@ public final class Settings { /** {@hide} */ @Readable + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("NoSettingsProvider") public static final String BLUETOOTH_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode"; /** {@hide} */ @@ -16706,6 +16750,30 @@ public final class Settings { public static final String RESTRICTED_NETWORKING_MODE = "restricted_networking_mode"; /** + * Setting indicating whether Low Power Standby is enabled, if supported. + * + * Values are: + * 0: disabled + * 1: enabled + * + * @hide + */ + public static final String LOW_POWER_STANDBY_ENABLED = "low_power_standby_enabled"; + + /** + * Setting indicating whether Low Power Standby is allowed to be active during doze + * maintenance mode. + * + * Values are: + * 0: Low Power Standby will be disabled during doze maintenance mode + * 1: Low Power Standby can be active during doze maintenance mode + * + * @hide + */ + public static final String LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE = + "low_power_standby_active_during_maintenance"; + + /** * Settings migrated from Wear OS settings provider. * @hide */ 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/Dataset.java b/core/java/android/service/autofill/Dataset.java index 86341a908ad7..cfb690916ecc 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -65,6 +65,16 @@ import java.util.regex.Pattern; * can be shown by the keyboard as a suggestion. To use this feature, the Dataset should contain * an {@link InlinePresentation} representing how the inline suggestion UI will be rendered. * + * <a name="FillDialogUI"></a> + * <h3>Fill Dialog UI</h3> + * + * <p>The fill dialog UI is a more conspicuous and efficient interface than dropdown UI. If autofill + * suggestions are available when the user clicks on a field that supports filling the dialog UI, + * Autofill will pop up a fill dialog. The dialog will take up a larger area to display the + * datasets, so it is easy for users to pay attention to the datasets and selecting a dataset. + * If the user focuses on the view before suggestions are available, will fall back to dropdown UI + * or inline suggestions. + * * <a name="Authentication"></a> * <h3>Dataset authentication</h3> * @@ -92,10 +102,9 @@ import java.util.regex.Pattern; * <ol> * <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not * {@link AutofillValue#isText() text} or is empty, all datasets are shown. - * <li>Datasets that have a filter regex (set through - * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or - * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose - * regex matches the view's text value converted to lower case are shown. + * <li>Datasets that have a filter regex (set through {@link Field.Builder#setFilter(Pattern)} + * and {@link Dataset.Builder#setField(AutofillId, Field)}) and whose regex matches the view's + * text value converted to lower case are shown. * <li>Datasets that do not require authentication, have a field value that is * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts * with the lower case value of the view's text are shown. @@ -107,11 +116,13 @@ public final class Dataset implements Parcelable { private final ArrayList<AutofillId> mFieldIds; private final ArrayList<AutofillValue> mFieldValues; private final ArrayList<RemoteViews> mFieldPresentations; + private final ArrayList<RemoteViews> mFieldDialogPresentations; private final ArrayList<InlinePresentation> mFieldInlinePresentations; private final ArrayList<InlinePresentation> mFieldInlineTooltipPresentations; private final ArrayList<DatasetFieldFilter> mFieldFilters; @Nullable private final ClipData mFieldContent; private final RemoteViews mPresentation; + private final RemoteViews mDialogPresentation; @Nullable private final InlinePresentation mInlinePresentation; @Nullable private final InlinePresentation mInlineTooltipPresentation; private final IntentSender mAuthentication; @@ -121,11 +132,13 @@ public final class Dataset implements Parcelable { mFieldIds = builder.mFieldIds; mFieldValues = builder.mFieldValues; mFieldPresentations = builder.mFieldPresentations; + mFieldDialogPresentations = builder.mFieldDialogPresentations; mFieldInlinePresentations = builder.mFieldInlinePresentations; mFieldInlineTooltipPresentations = builder.mFieldInlineTooltipPresentations; mFieldFilters = builder.mFieldFilters; mFieldContent = builder.mFieldContent; mPresentation = builder.mPresentation; + mDialogPresentation = builder.mDialogPresentation; mInlinePresentation = builder.mInlinePresentation; mInlineTooltipPresentation = builder.mInlineTooltipPresentation; mAuthentication = builder.mAuthentication; @@ -153,6 +166,12 @@ public final class Dataset implements Parcelable { } /** @hide */ + public RemoteViews getFieldDialogPresentation(int index) { + final RemoteViews customPresentation = mFieldDialogPresentations.get(index); + return customPresentation != null ? customPresentation : mDialogPresentation; + } + + /** @hide */ public @Nullable InlinePresentation getFieldInlinePresentation(int index) { final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(index); return inlinePresentation != null ? inlinePresentation : mInlinePresentation; @@ -219,6 +238,9 @@ public final class Dataset implements Parcelable { if (mFieldPresentations != null) { builder.append(", fieldPresentations=").append(mFieldPresentations.size()); } + if (mFieldDialogPresentations != null) { + builder.append(", fieldDialogPresentations=").append(mFieldDialogPresentations.size()); + } if (mFieldInlinePresentations != null) { builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size()); } @@ -232,6 +254,9 @@ public final class Dataset implements Parcelable { if (mPresentation != null) { builder.append(", hasPresentation"); } + if (mDialogPresentation != null) { + builder.append(", hasDialogPresentation"); + } if (mInlinePresentation != null) { builder.append(", hasInlinePresentation"); } @@ -264,11 +289,13 @@ public final class Dataset implements Parcelable { private ArrayList<AutofillId> mFieldIds; private ArrayList<AutofillValue> mFieldValues; private ArrayList<RemoteViews> mFieldPresentations; + private ArrayList<RemoteViews> mFieldDialogPresentations; private ArrayList<InlinePresentation> mFieldInlinePresentations; private ArrayList<InlinePresentation> mFieldInlineTooltipPresentations; private ArrayList<DatasetFieldFilter> mFieldFilters; @Nullable private ClipData mFieldContent; private RemoteViews mPresentation; + private RemoteViews mDialogPresentation; @Nullable private InlinePresentation mInlinePresentation; @Nullable private InlinePresentation mInlineTooltipPresentation; private IntentSender mAuthentication; @@ -279,7 +306,9 @@ public final class Dataset implements Parcelable { * Creates a new builder. * * @param presentation The presentation used to visualize this dataset. + * @deprecated Use {@link #Builder(Presentations)} instead. */ + @Deprecated public Builder(@NonNull RemoteViews presentation) { Objects.requireNonNull(presentation, "presentation must be non-null"); mPresentation = presentation; @@ -294,19 +323,34 @@ public final class Dataset implements Parcelable { * as inline suggestions. If the dataset supports inline suggestions, * this should not be null. * @hide + * @deprecated Use {@link #Builder(Presentations)} instead. */ @SystemApi + @Deprecated public Builder(@NonNull InlinePresentation inlinePresentation) { Objects.requireNonNull(inlinePresentation, "inlinePresentation must be non-null"); mInlinePresentation = inlinePresentation; } /** + * Creates a new builder. + * + * @param presentations The presentations used to visualize this dataset. + */ + public Builder(@NonNull Presentations presentations) { + Objects.requireNonNull(presentations, "presentations must be non-null"); + + mPresentation = presentations.getMenuPresentation(); + mInlinePresentation = presentations.getInlinePresentation(); + mInlineTooltipPresentation = presentations.getInlineTooltipPresentation(); + mDialogPresentation = presentations.getDialogPresentation(); + } + + /** * Creates a new builder for a dataset where each field will be visualized independently. * - * <p>When using this constructor, fields must be set through - * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or - * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}. + * <p>When using this constructor, a presentation must be provided for each field through + * {@link #setField(AutofillId, Field)}. */ public Builder() { } @@ -318,7 +362,9 @@ public final class Dataset implements Parcelable { * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. + * @deprecated Use {@link #Builder(Presentations)} instead. */ + @Deprecated public @NonNull Builder setInlinePresentation( @NonNull InlinePresentation inlinePresentation) { throwIfDestroyed(); @@ -339,7 +385,9 @@ public final class Dataset implements Parcelable { * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. + * @deprecated Use {@link #Builder(Presentations)} instead. */ + @Deprecated public @NonNull Builder setInlinePresentation( @NonNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation) { @@ -479,7 +527,7 @@ public final class Dataset implements Parcelable { "Content items cannot contain an Intent: content=" + content); } } - setLifeTheUniverseAndEverything(id, null, null, null, null); + setLifeTheUniverseAndEverything(id, null, null, null, null, null, null); mFieldContent = content; return this; } @@ -509,10 +557,12 @@ public final class Dataset implements Parcelable { * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. + * @deprecated Use {@link #setField(AutofillId, Field)} instead. */ + @Deprecated public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) { throwIfDestroyed(); - setLifeTheUniverseAndEverything(id, value, null, null, null); + setLifeTheUniverseAndEverything(id, value, null, null, null, null, null); return this; } @@ -537,12 +587,14 @@ public final class Dataset implements Parcelable { * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. + * @deprecated Use {@link #setField(AutofillId, Field)} instead. */ + @Deprecated public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation) { throwIfDestroyed(); Objects.requireNonNull(presentation, "presentation cannot be null"); - setLifeTheUniverseAndEverything(id, value, presentation, null, null); + setLifeTheUniverseAndEverything(id, value, presentation, null, null, null, null); return this; } @@ -572,13 +624,16 @@ public final class Dataset implements Parcelable { * @return this builder. * @throws IllegalStateException if the builder was constructed without a * {@link RemoteViews presentation} or {@link #build()} was already called. + * @deprecated Use {@link #setField(AutofillId, Field)} instead. */ + @Deprecated public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter) { throwIfDestroyed(); Preconditions.checkState(mPresentation != null, "Dataset presentation not set on constructor"); - setLifeTheUniverseAndEverything(id, value, null, null, new DatasetFieldFilter(filter)); + setLifeTheUniverseAndEverything( + id, value, null, null, null, new DatasetFieldFilter(filter), null); return this; } @@ -610,13 +665,15 @@ public final class Dataset implements Parcelable { * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. + * @deprecated Use {@link #setField(AutofillId, Field)} instead. */ + @Deprecated public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation) { throwIfDestroyed(); Objects.requireNonNull(presentation, "presentation cannot be null"); - setLifeTheUniverseAndEverything(id, value, presentation, null, - new DatasetFieldFilter(filter)); + setLifeTheUniverseAndEverything(id, value, presentation, null, null, + new DatasetFieldFilter(filter), null); return this; } @@ -641,13 +698,16 @@ public final class Dataset implements Parcelable { * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. + * @deprecated Use {@link #setField(AutofillId, Field)} instead. */ + @Deprecated public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) { throwIfDestroyed(); Objects.requireNonNull(presentation, "presentation cannot be null"); Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null"); - setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null); + setLifeTheUniverseAndEverything( + id, value, presentation, inlinePresentation, null, null, null); return this; } @@ -672,7 +732,9 @@ public final class Dataset implements Parcelable { * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. + * @deprecated Use {@link #setField(AutofillId, Field)} instead. */ + @Deprecated public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, @NonNull InlinePresentation inlineTooltipPresentation) { @@ -682,7 +744,7 @@ public final class Dataset implements Parcelable { Objects.requireNonNull(inlineTooltipPresentation, "inlineTooltipPresentation cannot be null"); setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, - inlineTooltipPresentation, null); + inlineTooltipPresentation, null, null); return this; } @@ -718,15 +780,17 @@ public final class Dataset implements Parcelable { * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. + * @deprecated Use {@link #setField(AutofillId, Field)} instead. */ + @Deprecated public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) { throwIfDestroyed(); Objects.requireNonNull(presentation, "presentation cannot be null"); Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null"); - setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, - new DatasetFieldFilter(filter)); + setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null, + new DatasetFieldFilter(filter), null); return this; } @@ -756,7 +820,9 @@ public final class Dataset implements Parcelable { * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. + * @deprecated Use {@link #setField(AutofillId, Field)} instead. */ + @Deprecated public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation, @@ -767,7 +833,91 @@ public final class Dataset implements Parcelable { Objects.requireNonNull(inlineTooltipPresentation, "inlineTooltipPresentation cannot be null"); setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, - inlineTooltipPresentation, new DatasetFieldFilter(filter)); + inlineTooltipPresentation, new DatasetFieldFilter(filter), null); + return this; + } + + /** + * Sets the value of a field. + * + * Before Android 13, this information could be provided using several overloaded + * setValue(...) methods. This method replaces those with a Builder pattern. + * For example, in the old workflow, the app sets a field would be: + * <pre class="prettyprint"> + * Dataset.Builder dataset = new Dataset.Builder(); + * if (filter != null) { + * if (presentation != null) { + * if (inlinePresentation != null) { + * dataset.setValue(id, value, filter, presentation, inlinePresentation) + * } else { + * dataset.setValue(id, value, filter, presentation); + * } + * } else { + * dataset.setValue(id, value, filter); + * } + * } else { + * if (presentation != null) { + * if (inlinePresentation != null) { + * dataset.setValue(id, value, presentation, inlinePresentation) + * } else { + * dataset.setValue(id, value, presentation); + * } + * } else { + * dataset.setValue(id, value); + * } + * } + * </pre> + * <p>The new workflow would be: + * <pre class="prettyprint"> + * Field.Builder fieldBuilder = new Field.Builder(); + * if (value != null) { + * fieldBuilder.setValue(value); + * } + * if (filter != null) { + * fieldBuilder.setFilter(filter); + * } + * Presentations.Builder presentationsBuilder = new Presentations.Builder(id); + * if (presentation != null) { + * presentationsBuilder.setMenuPresentation(presentation); + * } + * if (inlinePresentation != null) { + * presentationsBuilder.setInlinePresentation(inlinePresentation); + * } + * if (dialogPresentation != null) { + * presentationsBuilder.setDialogPresentation(dialogPresentation); + * } + * fieldBuilder.setPresentations(presentationsBuilder.build()); + * dataset.setField(id, fieldBuilder.build()); + * </pre> + * + * @see Field + * + * @param id id returned by {@link + * android.app.assist.AssistStructure.ViewNode#getAutofillId()}. + * @param field the fill information about the field. + * + * @throws IllegalStateException if {@link #build()} was already called. + * + * @return this builder. + */ + public @NonNull Builder setField(@NonNull AutofillId id, @Nullable Field field) { + throwIfDestroyed(); + if (field == null) { + setLifeTheUniverseAndEverything(id, null, null, null, null, null, null); + } else { + final DatasetFieldFilter filter = field.getFilter(); + final Presentations presentations = field.getPresentations(); + if (presentations == null) { + setLifeTheUniverseAndEverything(id, field.getValue(), null, null, null, + filter, null); + } else { + setLifeTheUniverseAndEverything(id, field.getValue(), + presentations.getMenuPresentation(), + presentations.getInlinePresentation(), + presentations.getInlineTooltipPresentation(), filter, + presentations.getDialogPresentation()); + } + } return this; } @@ -793,39 +943,34 @@ public final class Dataset implements Parcelable { * @throws IllegalStateException if {@link #build()} was already called. * * @return this builder. - * + * @deprecated Use {@link #setField(AutofillId, Field)} instead. * @hide */ + @Deprecated @SystemApi public @NonNull Builder setFieldInlinePresentation(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable Pattern filter, @NonNull InlinePresentation inlinePresentation) { throwIfDestroyed(); Objects.requireNonNull(inlinePresentation, "inlinePresentation cannot be null"); - setLifeTheUniverseAndEverything(id, value, null, inlinePresentation, - new DatasetFieldFilter(filter)); + setLifeTheUniverseAndEverything(id, value, null, inlinePresentation, null, + new DatasetFieldFilter(filter), null); return this; } private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable RemoteViews presentation, @Nullable InlinePresentation inlinePresentation, - @Nullable DatasetFieldFilter filter) { - setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null, - filter); - } - - private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, - @Nullable AutofillValue value, @Nullable RemoteViews presentation, - @Nullable InlinePresentation inlinePresentation, @Nullable InlinePresentation tooltip, - @Nullable DatasetFieldFilter filter) { + @Nullable DatasetFieldFilter filter, + @Nullable RemoteViews dialogPresentation) { Objects.requireNonNull(id, "id cannot be null"); if (mFieldIds != null) { final int existingIdx = mFieldIds.indexOf(id); if (existingIdx >= 0) { mFieldValues.set(existingIdx, value); mFieldPresentations.set(existingIdx, presentation); + mFieldDialogPresentations.set(existingIdx, dialogPresentation); mFieldInlinePresentations.set(existingIdx, inlinePresentation); mFieldInlineTooltipPresentations.set(existingIdx, tooltip); mFieldFilters.set(existingIdx, filter); @@ -835,6 +980,7 @@ public final class Dataset implements Parcelable { mFieldIds = new ArrayList<>(); mFieldValues = new ArrayList<>(); mFieldPresentations = new ArrayList<>(); + mFieldDialogPresentations = new ArrayList<>(); mFieldInlinePresentations = new ArrayList<>(); mFieldInlineTooltipPresentations = new ArrayList<>(); mFieldFilters = new ArrayList<>(); @@ -842,6 +988,7 @@ public final class Dataset implements Parcelable { mFieldIds.add(id); mFieldValues.add(value); mFieldPresentations.add(presentation); + mFieldDialogPresentations.add(dialogPresentation); mFieldInlinePresentations.add(inlinePresentation); mFieldInlineTooltipPresentations.add(tooltip); mFieldFilters.add(filter); @@ -853,10 +1000,7 @@ public final class Dataset implements Parcelable { * <p>You should not interact with this builder once this method is called. * * @throws IllegalStateException if no field was set (through - * {@link #setValue(AutofillId, AutofillValue)} or - * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or - * {@link #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation)}), - * or if {@link #build()} was already called. + * {@link #setField(AutofillId, Field)}), or if {@link #build()} was already called. * * @return The built dataset. */ @@ -897,11 +1041,13 @@ public final class Dataset implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeParcelable(mPresentation, flags); + parcel.writeParcelable(mDialogPresentation, flags); parcel.writeParcelable(mInlinePresentation, flags); parcel.writeParcelable(mInlineTooltipPresentation, flags); parcel.writeTypedList(mFieldIds, flags); parcel.writeTypedList(mFieldValues, flags); parcel.writeTypedList(mFieldPresentations, flags); + parcel.writeTypedList(mFieldDialogPresentations, flags); parcel.writeTypedList(mFieldInlinePresentations, flags); parcel.writeTypedList(mFieldInlineTooltipPresentations, flags); parcel.writeTypedList(mFieldFilters, flags); @@ -913,8 +1059,12 @@ public final class Dataset implements Parcelable { public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() { @Override public Dataset createFromParcel(Parcel parcel) { - final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class); - final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); + final RemoteViews presentation = parcel.readParcelable(null, + android.widget.RemoteViews.class); + final RemoteViews dialogPresentation = parcel.readParcelable(null, + android.widget.RemoteViews.class); + final InlinePresentation inlinePresentation = parcel.readParcelable(null, + android.service.autofill.InlinePresentation.class); final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); final ArrayList<AutofillId> ids = @@ -923,27 +1073,41 @@ public final class Dataset implements Parcelable { parcel.createTypedArrayList(AutofillValue.CREATOR); final ArrayList<RemoteViews> presentations = parcel.createTypedArrayList(RemoteViews.CREATOR); + final ArrayList<RemoteViews> dialogPresentations = + parcel.createTypedArrayList(RemoteViews.CREATOR); final ArrayList<InlinePresentation> inlinePresentations = parcel.createTypedArrayList(InlinePresentation.CREATOR); final ArrayList<InlinePresentation> inlineTooltipPresentations = parcel.createTypedArrayList(InlinePresentation.CREATOR); final ArrayList<DatasetFieldFilter> filters = parcel.createTypedArrayList(DatasetFieldFilter.CREATOR); - final ClipData fieldContent = parcel.readParcelable(null, android.content.ClipData.class); - final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class); + final ClipData fieldContent = parcel.readParcelable(null, + android.content.ClipData.class); + final IntentSender authentication = parcel.readParcelable(null, + android.content.IntentSender.class); final String datasetId = parcel.readString(); // Always go through the builder to ensure the data ingested by // the system obeys the contract of the builder to avoid attacks // using specially crafted parcels. - final Builder builder = (presentation != null) ? new Builder(presentation) - : new Builder(); - if (inlinePresentation != null) { + final Builder builder; + if (presentation != null || inlinePresentation != null || dialogPresentation != null) { + final Presentations.Builder presentationsBuilder = new Presentations.Builder(); + if (presentation != null) { + presentationsBuilder.setMenuPresentation(presentation); + } + if (inlinePresentation != null) { + presentationsBuilder.setInlinePresentation(inlinePresentation); + } if (inlineTooltipPresentation != null) { - builder.setInlinePresentation(inlinePresentation, inlineTooltipPresentation); - } else { - builder.setInlinePresentation(inlinePresentation); + presentationsBuilder.setInlineTooltipPresentation(inlineTooltipPresentation); + } + if (dialogPresentation != null) { + presentationsBuilder.setDialogPresentation(dialogPresentation); } + builder = new Builder(presentationsBuilder.build()); + } else { + builder = new Builder(); } if (fieldContent != null) { @@ -954,13 +1118,15 @@ public final class Dataset implements Parcelable { final AutofillId id = ids.get(i); final AutofillValue value = values.get(i); final RemoteViews fieldPresentation = presentations.get(i); + final RemoteViews fieldDialogPresentation = dialogPresentations.get(i); final InlinePresentation fieldInlinePresentation = i < inlinePresentationsSize ? inlinePresentations.get(i) : null; final InlinePresentation fieldInlineTooltipPresentation = i < inlinePresentationsSize ? inlineTooltipPresentations.get(i) : null; final DatasetFieldFilter filter = filters.get(i); builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, - fieldInlinePresentation, fieldInlineTooltipPresentation, filter); + fieldInlinePresentation, fieldInlineTooltipPresentation, filter, + fieldDialogPresentation); } builder.setAuthentication(authentication); builder.setId(datasetId); @@ -986,7 +1152,7 @@ public final class Dataset implements Parcelable { @Nullable public final Pattern pattern; - private DatasetFieldFilter(@Nullable Pattern pattern) { + DatasetFieldFilter(@Nullable Pattern pattern) { this.pattern = pattern; } diff --git a/core/java/android/service/autofill/Field.java b/core/java/android/service/autofill/Field.java new file mode 100644 index 000000000000..b7c0d82f41f5 --- /dev/null +++ b/core/java/android/service/autofill/Field.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; + +import com.android.internal.util.DataClass; + +import java.util.regex.Pattern; + +/** + * This class is used to set all information of a field. Such as the + * {@link AutofillId} of the field, the {@link AutofillValue} to be autofilled, + * a <a href="#Filtering">explicit filter</a>, and presentations to be visualized, + * etc. + */ +public final class Field { + + /** + * The value to be autofilled. Pass {@code null} if you do not have the value + * but the target view is a logical part of the dataset. For example, if the + * dataset needs authentication and you have no access to the value. + */ + private @Nullable AutofillValue mValue; + + /** + * Regex used to determine if the dataset should be shown in the autofill UI; + * when {@code null}, it disables filtering on that dataset (this is the recommended + * approach when {@code value} is not {@code null} and field contains sensitive data + * such as passwords). + * + * @see Dataset.DatasetFieldFilter + * @hide + */ + private @Nullable Dataset.DatasetFieldFilter mFilter; + + /** + * The presentations used to visualize this field in Autofill UI. + */ + private @Nullable Presentations mPresentations; + + + /* package-private */ Field( + @Nullable AutofillValue value, + @Nullable Dataset.DatasetFieldFilter filter, + @Nullable Presentations presentations) { + this.mValue = value; + this.mFilter = filter; + this.mPresentations = presentations; + } + + /** + * The value to be autofilled. Pass {@code null} if you do not have the value + * but the target view is a logical part of the dataset. For example, if the + * dataset needs authentication and you have no access to the value. + */ + @DataClass.Generated.Member + public @Nullable AutofillValue getValue() { + return mValue; + } + + /** + * Regex used to determine if the dataset should be shown in the autofill UI; + * when {@code null}, it disables filtering on that dataset (this is the recommended + * approach when {@code value} is not {@code null} and field contains sensitive data + * such as passwords). + * + * @see Dataset.DatasetFieldFilter + * @hide + */ + public @Nullable Dataset.DatasetFieldFilter getFilter() { + return mFilter; + } + + /** + * The presentations used to visualize this field in Autofill UI. + */ + public @Nullable Presentations getPresentations() { + return mPresentations; + } + + /** + * A builder for {@link Field} + */ + public static final class Builder { + + private @Nullable AutofillValue mValue = null; + private @Nullable Dataset.DatasetFieldFilter mFilter = null; + private @Nullable Presentations mPresentations = null; + private boolean mDestroyed = false; + + public Builder() { + } + + /** + * The value to be autofilled. Pass {@code null} if you do not have the value + * but the target view is a logical part of the dataset. For example, if the + * dataset needs authentication and you have no access to the value. + */ + public @NonNull Builder setValue(@NonNull AutofillValue value) { + checkNotUsed(); + mValue = value; + return this; + } + + /** + * Regex used to determine if the dataset should be shown in the autofill UI; + * when {@code null}, it disables filtering on that dataset (this is the recommended + * approach when {@code value} is not {@code null} and field contains sensitive data + * such as passwords). + */ + @SuppressLint("MissingGetterMatchingBuilder") + public @NonNull Builder setFilter(@Nullable Pattern value) { + checkNotUsed(); + mFilter = new Dataset.DatasetFieldFilter(value); + return this; + } + + /** + * The presentations used to visualize this field in Autofill UI. + */ + public @NonNull Builder setPresentations(@NonNull Presentations value) { + checkNotUsed(); + mPresentations = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull Field build() { + checkNotUsed(); + mDestroyed = true; // Mark builder used + + Field o = new Field( + mValue, + mFilter, + mPresentations); + return o; + } + + private void checkNotUsed() { + if (mDestroyed) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } +} diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index 43bd4102ffb5..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; @@ -98,6 +99,12 @@ public final class FillRequest implements Parcelable { // The flag value 0x20 has been defined in AutofillManager. + /** + * Indicates the request is coming from the activity just started. + * @hide + */ + public static final @RequestFlags int FLAG_ACTIVITY_START = 0x40; + /** @hide */ public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE; @@ -154,19 +161,32 @@ 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"); } - // Code below generated by codegen v1.0.15. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/FillRequest.java + // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/service/autofill/FillRequest.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -178,7 +198,8 @@ public final class FillRequest implements Parcelable { FLAG_MANUAL_REQUEST, FLAG_COMPATIBILITY_MODE_REQUEST, FLAG_PASSWORD_INPUT_TYPE, - FLAG_VIEW_NOT_FOCUSED + FLAG_VIEW_NOT_FOCUSED, + FLAG_ACTIVITY_START }) @Retention(RetentionPolicy.SOURCE) @DataClass.Generated.Member @@ -202,6 +223,8 @@ public final class FillRequest implements Parcelable { return "FLAG_PASSWORD_INPUT_TYPE"; case FLAG_VIEW_NOT_FOCUSED: return "FLAG_VIEW_NOT_FOCUSED"; + case FLAG_ACTIVITY_START: + return "FLAG_ACTIVITY_START"; default: return Integer.toHexString(value); } } @@ -243,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 @@ -251,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( @@ -264,8 +298,10 @@ public final class FillRequest implements Parcelable { FLAG_MANUAL_REQUEST | FLAG_COMPATIBILITY_MODE_REQUEST | FLAG_PASSWORD_INPUT_TYPE - | FLAG_VIEW_NOT_FOCUSED); + | FLAG_VIEW_NOT_FOCUSED + | FLAG_ACTIVITY_START); this.mInlineSuggestionsRequest = inlineSuggestionsRequest; + this.mDelayedFillIntentSender = delayedFillIntentSender; onConstructed(); } @@ -338,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() { @@ -349,7 +401,8 @@ public final class FillRequest implements Parcelable { "fillContexts = " + mFillContexts + ", " + "clientState = " + mClientState + ", " + "flags = " + requestFlagsToString(mFlags) + ", " + - "inlineSuggestionsRequest = " + mInlineSuggestionsRequest + + "inlineSuggestionsRequest = " + mInlineSuggestionsRequest + ", " + + "delayedFillIntentSender = " + mDelayedFillIntentSender + " }"; } @@ -362,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 @@ -384,10 +439,11 @@ public final class FillRequest implements Parcelable { byte flg = in.readByte(); int id = in.readInt(); List<FillContext> fillContexts = new ArrayList<>(); - in.readParcelableList(fillContexts, FillContext.class.getClassLoader(), android.service.autofill.FillContext.class); + in.readParcelableList(fillContexts, FillContext.class.getClassLoader()); 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; @@ -401,8 +457,10 @@ public final class FillRequest implements Parcelable { FLAG_MANUAL_REQUEST | FLAG_COMPATIBILITY_MODE_REQUEST | FLAG_PASSWORD_INPUT_TYPE - | FLAG_VIEW_NOT_FOCUSED); + | FLAG_VIEW_NOT_FOCUSED + | FLAG_ACTIVITY_START); this.mInlineSuggestionsRequest = inlineSuggestionsRequest; + this.mDelayedFillIntentSender = delayedFillIntentSender; onConstructed(); } @@ -422,10 +480,10 @@ public final class FillRequest implements Parcelable { }; @DataClass.Generated( - time = 1589280816805L, - codegenVersion = "1.0.15", + 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 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 d94988ebea66..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 {} @@ -79,11 +91,14 @@ public final class FillResponse implements Parcelable { private final @Nullable RemoteViews mPresentation; private final @Nullable InlinePresentation mInlinePresentation; private final @Nullable InlinePresentation mInlineTooltipPresentation; + private final @Nullable RemoteViews mDialogPresentation; + private final @Nullable RemoteViews mDialogHeader; private final @Nullable RemoteViews mHeader; private final @Nullable RemoteViews mFooter; private final @Nullable IntentSender mAuthentication; private final @Nullable AutofillId[] mAuthenticationIds; private final @Nullable AutofillId[] mIgnoredIds; + private final @Nullable AutofillId[] mFillDialogTriggerIds; private final long mDisableDuration; private final @Nullable AutofillId[] mFieldClassificationIds; private final int mFlags; @@ -99,10 +114,13 @@ public final class FillResponse implements Parcelable { mPresentation = builder.mPresentation; mInlinePresentation = builder.mInlinePresentation; mInlineTooltipPresentation = builder.mInlineTooltipPresentation; + mDialogPresentation = builder.mDialogPresentation; + mDialogHeader = builder.mDialogHeader; mHeader = builder.mHeader; mFooter = builder.mFooter; mAuthentication = builder.mAuthentication; mAuthenticationIds = builder.mAuthenticationIds; + mFillDialogTriggerIds = builder.mFillDialogTriggerIds; mIgnoredIds = builder.mIgnoredIds; mDisableDuration = builder.mDisableDuration; mFieldClassificationIds = builder.mFieldClassificationIds; @@ -144,6 +162,16 @@ public final class FillResponse implements Parcelable { } /** @hide */ + public @Nullable RemoteViews getDialogPresentation() { + return mDialogPresentation; + } + + /** @hide */ + public @Nullable RemoteViews getDialogHeader() { + return mDialogHeader; + } + + /** @hide */ public @Nullable RemoteViews getHeader() { return mHeader; } @@ -164,6 +192,11 @@ public final class FillResponse implements Parcelable { } /** @hide */ + public @Nullable AutofillId[] getFillDialogTriggerIds() { + return mFillDialogTriggerIds; + } + + /** @hide */ public @Nullable AutofillId[] getIgnoredIds() { return mIgnoredIds; } @@ -229,6 +262,8 @@ public final class FillResponse implements Parcelable { private RemoteViews mPresentation; private InlinePresentation mInlinePresentation; private InlinePresentation mInlineTooltipPresentation; + private RemoteViews mDialogPresentation; + private RemoteViews mDialogHeader; private RemoteViews mHeader; private RemoteViews mFooter; private IntentSender mAuthentication; @@ -236,6 +271,7 @@ public final class FillResponse implements Parcelable { private AutofillId[] mIgnoredIds; private long mDisableDuration; private AutofillId[] mFieldClassificationIds; + private AutofillId[] mFillDialogTriggerIds; private int mFlags; private boolean mDestroyed; private UserData mUserData; @@ -243,7 +279,7 @@ public final class FillResponse implements Parcelable { private boolean mSupportsInlineSuggestions; /** - * Triggers a custom UI before before autofilling the screen with any data set in this + * Triggers a custom UI before autofilling the screen with any data set in this * response. * * <p><b>Note:</b> Although the name of this method suggests that it should be used just for @@ -277,7 +313,7 @@ public final class FillResponse implements Parcelable { * example a credit card whose CVV needs to be entered. * * <p>If you provide an authentication intent you must also provide a presentation - * which is used to visualize visualize the response for triggering the authentication + * which is used to visualize the response for triggering the authentication * flow. * * <p><b>Note:</b> Do not make the provided pending intent @@ -306,7 +342,11 @@ public final class FillResponse implements Parcelable { * {@link #setFooter(RemoteViews) footer} are already set for this builder. * * @see android.app.PendingIntent#getIntentSender() + * @deprecated Use + * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)} + * instead. */ + @Deprecated @NonNull public Builder setAuthentication(@NonNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation) { @@ -327,7 +367,7 @@ public final class FillResponse implements Parcelable { } /** - * Triggers a custom UI before before autofilling the screen with any data set in this + * Triggers a custom UI before autofilling the screen with any data set in this * response. * * <p><b>Note:</b> Although the name of this method suggests that it should be used just for @@ -365,7 +405,11 @@ public final class FillResponse implements Parcelable { * {@link #setFooter(RemoteViews) footer} are already set for this builder. * * @see android.app.PendingIntent#getIntentSender() + * @deprecated Use + * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)} + * instead. */ + @Deprecated @NonNull public Builder setAuthentication(@NonNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation, @@ -374,13 +418,18 @@ public final class FillResponse implements Parcelable { } /** - * Triggers a custom UI before before autofilling the screen with any data set in this + * Triggers a custom UI before autofilling the screen with any data set in this * response. * * <p>This method like * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews, InlinePresentation)} * but allows setting an {@link InlinePresentation} for the inline suggestion tooltip. + * + * @deprecated Use + * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)} + * instead. */ + @Deprecated @NonNull public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids, @Nullable IntentSender authentication, @Nullable RemoteViews presentation, @@ -388,6 +437,105 @@ public final class FillResponse implements Parcelable { @Nullable InlinePresentation inlineTooltipPresentation) { throwIfDestroyed(); throwIfDisableAutofillCalled(); + return setAuthentication(ids, authentication, presentation, + inlinePresentation, inlineTooltipPresentation, null); + } + + /** + * Triggers a custom UI before autofilling the screen with any data set in this + * response. + * + * <p><b>Note:</b> Although the name of this method suggests that it should be used just for + * authentication flow, it can be used for other advanced flows; see {@link AutofillService} + * for examples. + * + * <p>This is typically useful when a user interaction is required to unlock their + * data vault if you encrypt the data set labels and data set data. It is recommended + * to encrypt only the sensitive data and not the data set labels which would allow + * auth on the data set level leading to a better user experience. Note that if you + * use sensitive data as a label, for example an email address, then it should also + * be encrypted. The provided {@link android.app.PendingIntent intent} must be an + * {@link Activity} which implements your authentication flow. Also if you provide an auth + * intent you also need to specify the presentation view to be shown in the fill UI + * for the user to trigger your authentication flow. + * + * <p>When a user triggers autofill, the system launches the provided intent + * whose extras will have the + * {@link android.view.autofill.AutofillManager#EXTRA_ASSIST_STRUCTURE screen + * content} and your {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE + * client state}. Once you complete your authentication flow you should set the + * {@link Activity} result to {@link android.app.Activity#RESULT_OK} and set the + * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra + * with the fully populated {@link FillResponse response} (or {@code null} if the screen + * cannot be autofilled). + * + * <p>For example, if you provided an empty {@link FillResponse response} because the + * user's data was locked and marked that the response needs an authentication then + * in the response returned if authentication succeeds you need to provide all + * available data sets some of which may need to be further authenticated, for + * example a credit card whose CVV needs to be entered. + * + * <p>If you provide an authentication intent you must also provide a presentation + * which is used to visualize the response for triggering the authentication + * flow. + * + * <p><b>Note:</b> Do not make the provided pending intent + * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the + * platform needs to fill in the authentication arguments. + * + * <p><b>Note:</b> {@link #setHeader(RemoteViews)} or {@link #setFooter(RemoteViews)} does + * not work with {@link InlinePresentation}.</p> + * + * @param ids id of Views that when focused will display the authentication UI. + * @param authentication Intent to an activity with your authentication flow. + * @param presentations The presentations to visualize the response. + * + * @throws IllegalArgumentException if any of the following occurs: + * <ul> + * <li>{@code ids} is {@code null}</li> + * <li>{@code ids} is empty</li> + * <li>{@code ids} contains a {@code null} element</li> + * <li>{@code authentication} is {@code null}, but either or both of + * {@code presentations.getPresentation()} and + * {@code presentations.getInlinePresentation()} is non-{@code null}</li> + * <li>{@code authentication} is non-{{@code null}, but both + * {@code presentations.getPresentation()} and + * {@code presentations.getInlinePresentation()} are {@code null}</li> + * </ul> + * + * @throws IllegalStateException if a {@link #setHeader(RemoteViews) header} or a + * {@link #setFooter(RemoteViews) footer} are already set for this builder. + * + * @return This builder. + */ + @NonNull + public Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids, + @Nullable IntentSender authentication, + @Nullable Presentations presentations) { + throwIfDestroyed(); + throwIfDisableAutofillCalled(); + if (presentations == null) { + return setAuthentication(ids, authentication, null, null, null, null); + } + return setAuthentication(ids, authentication, + presentations.getMenuPresentation(), + presentations.getInlinePresentation(), + presentations.getInlineTooltipPresentation(), + presentations.getDialogPresentation()); + } + + /** + * Triggers a custom UI before autofilling the screen with any data set in this + * response. + */ + @NonNull + private Builder setAuthentication(@SuppressLint("ArrayReturn") @NonNull AutofillId[] ids, + @Nullable IntentSender authentication, @Nullable RemoteViews presentation, + @Nullable InlinePresentation inlinePresentation, + @Nullable InlinePresentation inlineTooltipPresentation, + @Nullable RemoteViews dialogPresentation) { + throwIfDestroyed(); + throwIfDisableAutofillCalled(); if (mHeader != null || mFooter != null) { throw new IllegalStateException("Already called #setHeader() or #setFooter()"); } @@ -400,6 +548,7 @@ public final class FillResponse implements Parcelable { mPresentation = presentation; mInlinePresentation = inlinePresentation; mInlineTooltipPresentation = inlineTooltipPresentation; + mDialogPresentation = dialogPresentation; mAuthenticationIds = assertValid(ids); return this; } @@ -520,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; } @@ -552,7 +701,7 @@ public final class FillResponse implements Parcelable { * * @throws IllegalArgumentException if {@code duration} is not a positive number. * @throws IllegalStateException if either {@link #addDataset(Dataset)}, - * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, + * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}, * {@link #setSaveInfo(SaveInfo)}, {@link #setClientState(Bundle)}, or * {@link #setFieldClassificationIds(AutofillId...)} was already called. */ @@ -591,8 +740,8 @@ public final class FillResponse implements Parcelable { * @return this builder * * @throws IllegalStateException if an - * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) authentication} was - * already set for this builder. + * {@link #setAuthentication(AutofillId[], IntentSender, Presentations) + * authentication} was already set for this builder. */ // TODO(b/69796626): make it sticky / update javadoc @NonNull @@ -623,7 +772,7 @@ public final class FillResponse implements Parcelable { * @return this builder * * @throws IllegalStateException if the FillResponse - * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) + * {@link #setAuthentication(AutofillId[], IntentSender, Presentations) * requires authentication}. */ // TODO(b/69796626): make it sticky / update javadoc @@ -643,7 +792,7 @@ public final class FillResponse implements Parcelable { * * @return this builder * @throws IllegalStateException if the FillResponse - * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews) + * {@link #setAuthentication(AutofillId[], IntentSender, Presentations) * requires authentication}. */ @NonNull @@ -674,13 +823,46 @@ public final class FillResponse implements Parcelable { } /** + * Sets the presentation of header in fill dialog UI. The header should have + * a prompt for what datasets are shown in the dialog. If this is not set, + * the dialog only shows your application icon. + * + * More details about the fill dialog, see + * <a href="Dataset.html#FillDialogUI">fill dialog UI</a> + */ + @NonNull + public Builder setDialogHeader(@NonNull RemoteViews header) { + throwIfDestroyed(); + Objects.requireNonNull(header); + mDialogHeader = header; + return this; + } + + /** + * Sets which fields are used for the fill dialog UI. + * + * More details about the fill dialog, see + * <a href="Dataset.html#FillDialogUI">fill dialog UI</a> + * + * @throws IllegalStateException if {@link #build()} was already called. + * @throws NullPointerException if {@code ids} or any element on it is {@code null}. + */ + @NonNull + public Builder setFillDialogTriggerIds(@NonNull AutofillId... ids) { + throwIfDestroyed(); + Preconditions.checkArrayElementsNotNull(ids, "ids"); + mFillDialogTriggerIds = ids; + return this; + } + + /** * Builds a new {@link FillResponse} instance. * * @throws IllegalStateException if any of the following conditions occur: * <ol> * <li>{@link #build()} was already called. * <li>No call was made to {@link #addDataset(Dataset)}, - * {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, + * {@link #setAuthentication(AutofillId[], IntentSender, Presentations)}, * {@link #setSaveInfo(SaveInfo)}, {@link #disableAutofill(long)}, * {@link #setClientState(Bundle)}, * or {@link #setFieldClassificationIds(AutofillId...)}. @@ -767,6 +949,12 @@ public final class FillResponse implements Parcelable { if (mInlineTooltipPresentation != null) { builder.append(", hasInlineTooltipPresentation"); } + if (mDialogPresentation != null) { + builder.append(", hasDialogPresentation"); + } + if (mDialogHeader != null) { + builder.append(", hasDialogHeader"); + } if (mHeader != null) { builder.append(", hasHeader"); } @@ -779,6 +967,10 @@ public final class FillResponse implements Parcelable { if (mAuthenticationIds != null) { builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds)); } + if (mFillDialogTriggerIds != null) { + builder.append(", fillDialogTriggerIds=") + .append(Arrays.toString(mFillDialogTriggerIds)); + } builder.append(", disableDuration=").append(mDisableDuration); if (mFlags != 0) { builder.append(", flags=").append(mFlags); @@ -815,6 +1007,9 @@ public final class FillResponse implements Parcelable { parcel.writeParcelable(mPresentation, flags); parcel.writeParcelable(mInlinePresentation, flags); parcel.writeParcelable(mInlineTooltipPresentation, flags); + parcel.writeParcelable(mDialogPresentation, flags); + parcel.writeParcelable(mDialogHeader, flags); + parcel.writeParcelableArray(mFillDialogTriggerIds, flags); parcel.writeParcelable(mHeader, flags); parcel.writeParcelable(mFooter, flags); parcel.writeParcelable(mUserData, flags); @@ -850,9 +1045,18 @@ public final class FillResponse implements Parcelable { final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class); final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class); + final RemoteViews dialogPresentation = parcel.readParcelable(null, android.widget.RemoteViews.class); if (authenticationIds != null) { builder.setAuthentication(authenticationIds, authentication, presentation, - inlinePresentation, inlineTooltipPresentation); + inlinePresentation, inlineTooltipPresentation, dialogPresentation); + } + final RemoteViews dialogHeader = parcel.readParcelable(null, android.widget.RemoteViews.class); + if (dialogHeader != null) { + builder.setDialogHeader(dialogHeader); + } + final AutofillId[] triggerIds = parcel.readParcelableArray(null, AutofillId.class); + if (triggerIds != null) { + builder.setFillDialogTriggerIds(triggerIds); } final RemoteViews header = parcel.readParcelable(null, android.widget.RemoteViews.class); if (header != null) { diff --git a/core/java/android/service/autofill/Presentations.java b/core/java/android/service/autofill/Presentations.java new file mode 100644 index 000000000000..e8ac628ebd12 --- /dev/null +++ b/core/java/android/service/autofill/Presentations.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.widget.RemoteViews; + +import com.android.internal.util.DataClass; + +/** + * Holds presentations used to visualize autofill suggestions for each available UI type. + * + * @see Field + */ +@DataClass(genBuilder = true) +public final class Presentations { + + /** + * The presentation used to visualize this field in fill UI. + * + * <p>Note: Before Android 13, this was referred to simply as "presentation" in the SDK. + * + * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + */ + private @Nullable RemoteViews mMenuPresentation; + + /** + * The {@link InlinePresentation} used to visualize this dataset as inline suggestions. + * If the dataset supports inline suggestions, this should not be null. + */ + private @Nullable InlinePresentation mInlinePresentation; + + /** + * The presentation used to visualize this field in the + * <a href="Dataset.html#FillDialogUI">fill dialog UI</a> + * + * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + */ + private @Nullable RemoteViews mDialogPresentation; + + /** + * The {@link InlinePresentation} used to show the tooltip for the + * {@code mInlinePresentation}. If the set this field, the + * {@code mInlinePresentation} should not be null. + */ + private @Nullable InlinePresentation mInlineTooltipPresentation; + + private static RemoteViews defaultMenuPresentation() { + return null; + } + + private static InlinePresentation defaultInlinePresentation() { + return null; + } + + private static RemoteViews defaultDialogPresentation() { + return null; + } + + private static InlinePresentation defaultInlineTooltipPresentation() { + return null; + } + + private void onConstructed() { + if (mMenuPresentation == null + && mInlinePresentation == null + && mDialogPresentation == null) { + throw new IllegalStateException("All presentations are null."); + } + if (mInlineTooltipPresentation != null && mInlinePresentation == null) { + throw new IllegalStateException( + "The inline presentation is required for mInlineTooltipPresentation."); + } + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/service/autofill/Presentations.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + /* package-private */ Presentations( + @Nullable RemoteViews menuPresentation, + @Nullable InlinePresentation inlinePresentation, + @Nullable RemoteViews dialogPresentation, + @Nullable InlinePresentation inlineTooltipPresentation) { + this.mMenuPresentation = menuPresentation; + this.mInlinePresentation = inlinePresentation; + this.mDialogPresentation = dialogPresentation; + this.mInlineTooltipPresentation = inlineTooltipPresentation; + + onConstructed(); + } + + /** + * The presentation used to visualize this field in fill UI. + * + * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + */ + @DataClass.Generated.Member + public @Nullable RemoteViews getMenuPresentation() { + return mMenuPresentation; + } + + /** + * The {@link InlinePresentation} used to visualize this dataset as inline suggestions. + * If the dataset supports inline suggestions, this should not be null. + */ + @DataClass.Generated.Member + public @Nullable InlinePresentation getInlinePresentation() { + return mInlinePresentation; + } + + /** + * The presentation used to visualize this field in the fill dialog UI. + * + * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + */ + @DataClass.Generated.Member + public @Nullable RemoteViews getDialogPresentation() { + return mDialogPresentation; + } + + /** + * The {@link InlinePresentation} used to show the tooltip for the + * {@code mInlinePresentation}. If the set this field, the + * {@code mInlinePresentation} should not be null. + */ + @DataClass.Generated.Member + public @Nullable InlinePresentation getInlineTooltipPresentation() { + return mInlineTooltipPresentation; + } + + /** + * A builder for {@link Presentations} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @Nullable RemoteViews mMenuPresentation; + private @Nullable InlinePresentation mInlinePresentation; + private @Nullable RemoteViews mDialogPresentation; + private @Nullable InlinePresentation mInlineTooltipPresentation; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * The presentation used to visualize this field in fill UI. + * + * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + */ + @DataClass.Generated.Member + public @NonNull Builder setMenuPresentation(@NonNull RemoteViews value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mMenuPresentation = value; + return this; + } + + /** + * The {@link InlinePresentation} used to visualize this dataset as inline suggestions. + * If the dataset supports inline suggestions, this should not be null. + */ + @DataClass.Generated.Member + public @NonNull Builder setInlinePresentation(@NonNull InlinePresentation value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mInlinePresentation = value; + return this; + } + + /** + * The presentation used to visualize this field in the fill dialog UI. + * + * <p>Theme does not work with RemoteViews layout. Avoid hardcoded text color + * or background color: Autofill on different platforms may have different themes. + */ + @DataClass.Generated.Member + public @NonNull Builder setDialogPresentation(@NonNull RemoteViews value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mDialogPresentation = value; + return this; + } + + /** + * The {@link InlinePresentation} used to show the tooltip for the + * {@code mInlinePresentation}. If the set this field, the + * {@code mInlinePresentation} should not be null. + */ + @DataClass.Generated.Member + public @NonNull Builder setInlineTooltipPresentation(@NonNull InlinePresentation value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mInlineTooltipPresentation = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull Presentations build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mMenuPresentation = defaultMenuPresentation(); + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mInlinePresentation = defaultInlinePresentation(); + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mDialogPresentation = defaultDialogPresentation(); + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mInlineTooltipPresentation = defaultInlineTooltipPresentation(); + } + Presentations o = new Presentations( + mMenuPresentation, + mInlinePresentation, + mDialogPresentation, + mInlineTooltipPresentation); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x10) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1643083242164L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/service/autofill/Presentations.java", + inputSignatures = "private @android.annotation.Nullable android.widget.RemoteViews mMenuPresentation\nprivate @android.annotation.Nullable android.service.autofill.InlinePresentation mInlinePresentation\nprivate @android.annotation.Nullable android.widget.RemoteViews mDialogPresentation\nprivate @android.annotation.Nullable android.service.autofill.InlinePresentation mInlineTooltipPresentation\nprivate static android.widget.RemoteViews defaultMenuPresentation()\nprivate static android.service.autofill.InlinePresentation defaultInlinePresentation()\nprivate static android.widget.RemoteViews defaultDialogPresentation()\nprivate static android.service.autofill.InlinePresentation defaultInlineTooltipPresentation()\nprivate void onConstructed()\nclass Presentations extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl index 31352f14fe08..11e5ad8789f2 100644 --- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl +++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl @@ -37,5 +37,6 @@ interface IPersistentDataBlockService { boolean getOemUnlockEnabled(); int getFlashLockState(); boolean hasFrpCredentialHandle(); + String getPersistentDataPackageName(); } diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java index 44a886257d5a..9167153a0ef5 100644 --- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java +++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java @@ -26,8 +26,6 @@ import android.content.Context; import android.os.RemoteException; import android.service.oemlock.OemLockManager; -import com.android.internal.R; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -53,7 +51,6 @@ import java.lang.annotation.RetentionPolicy; @SystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE) public class PersistentDataBlockManager { private static final String TAG = PersistentDataBlockManager.class.getSimpleName(); - private final Context mContext; private IPersistentDataBlockService sService; /** @@ -78,10 +75,7 @@ public class PersistentDataBlockManager { public @interface FlashLockState {} /** @hide */ - public PersistentDataBlockManager( - Context context, - IPersistentDataBlockService service) { - mContext = context; + public PersistentDataBlockManager(IPersistentDataBlockService service) { sService = service; } @@ -219,7 +213,12 @@ public class PersistentDataBlockManager { */ @SystemApi @NonNull + @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public String getPersistentDataPackageName() { - return mContext.getString(R.string.config_persistentDataPackageName); + try { + return sService.getPersistentDataPackageName(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } } diff --git a/core/java/android/service/tracing/TraceReportService.java b/core/java/android/service/tracing/TraceReportService.java new file mode 100644 index 000000000000..3d16a3d41ea3 --- /dev/null +++ b/core/java/android/service/tracing/TraceReportService.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.tracing; + +import static android.annotation.SystemApi.Client.PRIVILEGED_APPS; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.tracing.TraceReportParams; +import android.util.Log; + +import java.io.IOException; +import java.util.UUID; + +/** + * Service to be sub-classed and exposed by (privileged) apps which want to report + * system traces. + * <p> + * Subclasses should implement the onReportTrace method to handle traces reported + * to them. + * </p> + * <pre> + * public class SampleReportService extends TraceReportService { + * public void onReportTrace(TraceParams args) { + * // --- Implementation goes here --- + * } + * } + * </pre> + * <p> + * The service declaration in the application manifest must specify + * BIND_TRACE_REPORT_SERVICE in the permission attribute. + * </p> + * <pre> + * <application> + * <service android:name=".SampleReportService" + * android:permission="android.permission.BIND_TRACE_REPORT_SERVICE"> + * </service> + * </application> + * </pre> + * + * Moreover, the package containing this service must hold the DUMP and PACKAGE_USAGE_STATS + * permissions. + * + * @hide + */ +@SystemApi(client = PRIVILEGED_APPS) +public class TraceReportService extends Service { + private static final String TAG = "TraceReportService"; + private Messenger mMessenger = null; + + /** + * Public to allow this to be used by TracingServiceProxy in system_server. + * + * @hide + */ + public static final int MSG_REPORT_TRACE = 1; + + /** + * Contains information about the trace which is being reported. + * + * @hide + */ + @SystemApi(client = PRIVILEGED_APPS) + public static final class TraceParams { + private final ParcelFileDescriptor mFd; + private final UUID mUuid; + + private TraceParams(TraceReportParams params) { + mFd = params.fd; + mUuid = new UUID(params.uuidMsb, params.uuidLsb); + } + + /** + * Returns the ParcelFileDescriptor for the collected trace. + */ + @NonNull + public ParcelFileDescriptor getFd() { + return mFd; + } + + /** + * Returns the UUID of the trace; this is exactly the UUID created by the tracing system + * (i.e. Perfetto) and is also present inside the trace file. + */ + @NonNull + public UUID getUuid() { + return mUuid; + } + } + + // Methods to override. + /** + * Called when a trace is reported and sent to this class. + * + * Note: the trace file descriptor should not be persisted beyond the lifetime of this + * function as it is owned by the framework and will be closed immediately after this function + * returns: if future use of the fd is needed, it should be duped. + */ + public void onReportTrace(@NonNull TraceParams args) { + } + + // Optional methods to override. + // Realistically, these methods are internal implementation details but since this class is + // a SystemApi, it's better to err on the side of flexibility just in-case we need to override + // these methods down the line. + + /** + * Handles binder calls from system_server. + */ + public boolean onMessage(@NonNull Message msg) { + if (msg.what == MSG_REPORT_TRACE) { + if (!(msg.obj instanceof TraceReportParams)) { + Log.e(TAG, "Received invalid type for report trace message."); + return false; + } + TraceParams params = new TraceParams((TraceReportParams) msg.obj); + try { + onReportTrace(params); + } finally { + try { + params.getFd().close(); + } catch (IOException ignored) { + } + } + return true; + } + return false; + } + + /** + * Returns an IBinder for handling binder calls from system_server. + */ + @Nullable + @Override + public IBinder onBind(@NonNull Intent intent) { + if (mMessenger == null) { + mMessenger = new Messenger(new Handler(Looper.getMainLooper(), this::onMessage)); + } + return mMessenger.getBinder(); + } +}
\ No newline at end of file diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index fba61cfd801e..5bd423599f5d 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Service; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; @@ -310,9 +311,10 @@ public class TrustAgentService extends Service { * * @see #FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE * - * TODO(b/213631672): Add CTS tests + * TODO(b/213631672): Remove @hide and @TestApi * @hide */ + @TestApi public void onUserRequestedUnlock() { } 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/tracing/ITracingServiceProxy.aidl b/core/java/android/tracing/ITracingServiceProxy.aidl index 4520db3915a2..8029b88226b0 100644 --- a/core/java/android/tracing/ITracingServiceProxy.aidl +++ b/core/java/android/tracing/ITracingServiceProxy.aidl @@ -16,17 +16,25 @@ package android.tracing; +import android.tracing.TraceReportParams; + /** * Binder interface for the TracingServiceProxy running in system_server. * * {@hide} */ -interface ITracingServiceProxy -{ +interface ITracingServiceProxy { /** * Notifies system tracing app that a tracing session has ended. If a session is repurposed * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but * there is no buffer available to dump. */ oneway void notifyTraceSessionEnded(boolean sessionStolen); + + /** + * Notifies the specified service that a trace has been captured. The contents of |params| + * contains the intended recipient (package and class) of this trace as well as a file + * descriptor to an unlinked trace |fd| (i.e. an fd opened using O_TMPFILE). + */ + oneway void reportTrace(in TraceReportParams params); } diff --git a/core/java/android/tracing/TraceReportParams.aidl b/core/java/android/tracing/TraceReportParams.aidl new file mode 100644 index 000000000000..f57386c087ea --- /dev/null +++ b/core/java/android/tracing/TraceReportParams.aidl @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.tracing; + +import android.os.ParcelFileDescriptor; + +/* + * Parameters for a trace report. + * + * See ITracingServiceProxy::reportTrace for more details. + * + * @hide + */ +parcelable TraceReportParams { + // The package name containing the reporter service (see |reporterClassName|). + String reporterPackageName; + + // The class name of the reporter service. The framework will bind to this service and pass the + // trace fd and metadata to this class. + // This class should be "trusted" (in practice this means being a priv_app + having DUMP and + // USAGE_STATS permissions). + String reporterClassName; + + // The file descriptor for the trace file. This will be an unlinked file fd (i.e. created + // with O_TMPFILE); the intention is that reporter classes link this fd into a app-private + // folder for reporting when conditions are right (e.g. charging, on unmetered networks etc). + ParcelFileDescriptor fd; + + // The least-significant-bytes of the UUID of this trace. + long uuidLsb; + + // The most-significant-bytes of the UUID of this trace. + long uuidMsb; + + // Flag indicating whether, instead of passing the fd from the trace collector, to pass a + // pipe fd from system_server and send the file over it. + // + // This flag is necessary because there is no good way to write a CTS test where a helper + // priv_app (in terms of SELinux) is needed (this is because priv_apps are supposed to be + // preinstalled on the system partition). By creating a pipe in system_server we work around + // this restriction. Note that there is a maximum allowed file size if this flag is set + // (see TracingServiceProxy). Further note that, even though SELinux may be worked around, + // manifest (i.e. framework) permissions are still checked even if this flag is set. + boolean usePipeForTesting; +}
\ No newline at end of file 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/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index b461faf70296..7e0d887a8f79 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -26,6 +26,7 @@ import android.graphics.Rect; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.RemoteException; import android.view.accessibility.IAccessibilityEmbeddedConnection; import android.view.InsetsState; @@ -180,6 +181,36 @@ public class SurfaceControlViewHost { return mRemoteInterface; } + /** + * Forward a configuration to the remote SurfaceControlViewHost. + * This will cause View#onConfigurationChanged to be invoked on the remote + * end. This does not automatically cause the SurfaceControlViewHost + * to be resized. The root View of a SurfaceControlViewHost + * is more akin to a PopupWindow in that the size is user specified + * independent of configuration width and height. + * + * @param c The configuration to forward + */ + public void notifyConfigurationChanged(@NonNull Configuration c) { + try { + getRemoteInterface().onConfigurationChanged(c); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Tear down the remote SurfaceControlViewHost and cause + * View#onDetachedFromWindow to be invoked on the other side. + */ + public void notifyDetachedFromWindow() { + try { + getRemoteInterface().onDispatchDetachedFromWindow(); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 75592730067a..4ff7e2297ea0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8178,9 +8178,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // to User. Ideally View should handle the event when isVisibleToUser() // becomes true where it should issue notifyViewEntered(). afm.notifyViewEntered(this); + } else { + afm.enableFillRequestActivityStarted(); } } else if (!enter && !isFocused()) { afm.notifyViewExited(this); + } else if (enter) { + afm.enableFillRequestActivityStarted(); } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index eaa12e53c321..386b277156e3 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -563,6 +563,8 @@ public final class ViewRootImpl implements ViewParent, @Nullable int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED; boolean mPerformContentCapture; + boolean mPerformAutoFill; + boolean mReportNextDraw; /** @@ -842,6 +844,7 @@ public final class ViewRootImpl implements ViewParent, mPreviousTransparentRegion = new Region(); mFirst = true; // true for the first time the view is added mPerformContentCapture = true; // also true for the first time the view is added + mPerformAutoFill = true; mAdded = false; mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); @@ -4353,6 +4356,18 @@ public final class ViewRootImpl implements ViewParent, if (mPerformContentCapture) { performContentCaptureInitialReport(); } + + if (mPerformAutoFill) { + notifyEnterForAutoFillIfNeeded(); + } + } + + private void notifyEnterForAutoFillIfNeeded() { + mPerformAutoFill = false; + final AutofillManager afm = getAutofillManager(); + if (afm != null) { + afm.notifyViewEnteredForActivityStarted(mView); + } } /** diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index bb13c1e78964..60ccf67249b7 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -16,6 +16,7 @@ package android.view.autofill; +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; import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED; @@ -538,6 +539,8 @@ public final class AutofillManager { */ public static final int NO_SESSION = Integer.MAX_VALUE; + private static final boolean HAS_FILL_DIALOG_UI_FEATURE = false; + private final IAutoFillManager mService; private final Object mLock = new Object(); @@ -629,6 +632,29 @@ public final class AutofillManager { @GuardedBy("mLock") @Nullable private Executor mRequestCallbackExecutor; + /** + * Indicates whether there are any fields that need to do a fill request + * after the activity starts. + * + * Note: This field will be set to true multiple times if there are many + * autofillable views. So needs to check mIsFillRequested at the same time to + * avoid re-trigger autofill. + */ + private boolean mRequireAutofill; + + /** + * Indicates whether there is already a field to do a fill request after + * the activity started. + * + * Autofill will automatically trigger a fill request after activity + * start if there is any field is autofillable. But if there is a field that + * triggered autofill, it is unnecessary to trigger again through + * AutofillManager#notifyViewEnteredForActivityStarted. + */ + private boolean mIsFillRequested; + + @Nullable private List<AutofillId> mFillDialogTriggerIds; + /** @hide */ public interface AutofillClient { /** @@ -766,6 +792,8 @@ public final class AutofillManager { mContext = Objects.requireNonNull(context, "context cannot be null"); mService = service; mOptions = context.getAutofillOptions(); + mIsFillRequested = false; + mRequireAutofill = false; if (mOptions != null) { sDebug = (mOptions.loggingLevel & FLAG_ADD_CLIENT_DEBUG) != 0; @@ -1042,6 +1070,39 @@ public final class AutofillManager { notifyViewEntered(view, 0); } + /** + * The view is autofillable, marked to perform a fill request after layout if + * the field does not trigger a fill request. + * + * @hide + */ + public void enableFillRequestActivityStarted() { + mRequireAutofill = true; + } + + private boolean hasFillDialogUiFeature() { + return HAS_FILL_DIALOG_UI_FEATURE; + } + + /** + * Notify autofill to do a fill request while the activity started. + * + * @hide + */ + public void notifyViewEnteredForActivityStarted(@NonNull View view) { + if (!hasAutofillFeature() || !hasFillDialogUiFeature()) { + return; + } + + if (!mRequireAutofill || mIsFillRequested) { + return; + } + + int flags = FLAG_ACTIVITY_START; + flags |= FLAG_VIEW_NOT_FOCUSED; + notifyViewEntered(view, flags); + } + @GuardedBy("mLock") private boolean shouldIgnoreViewEnteredLocked(@NonNull AutofillId id, int flags) { if (isDisabledByServiceLocked()) { @@ -1082,6 +1143,7 @@ public final class AutofillManager { } AutofillCallback callback; synchronized (mLock) { + mIsFillRequested = true; callback = notifyViewEnteredLocked(view, flags); } @@ -2026,6 +2088,8 @@ public final class AutofillManager { mFillableIds = null; mSaveTriggerId = null; mIdShownFillUi = null; + mIsFillRequested = false; + mRequireAutofill = false; if (resetEnteredIds) { mEnteredIds = null; } @@ -3031,6 +3095,29 @@ public final class AutofillManager { client.autofillClientRunOnUiThread(runnable); } + private void setFillDialogTriggerIds(@Nullable List<AutofillId> ids) { + mFillDialogTriggerIds = ids; + } + + /** + * Checks the id of autofill whether supported the fill dialog. + * + * @hide + */ + public boolean isShowFillDialog(AutofillId id) { + if (!hasFillDialogUiFeature() || mFillDialogTriggerIds == null) { + return false; + } + final int size = mFillDialogTriggerIds.size(); + for (int i = 0; i < size; i++) { + AutofillId fillId = mFillDialogTriggerIds.get(i); + if (fillId.equalsIgnoreSession(id)) { + return true; + } + } + return false; + } + /** * Implementation of the accessibility based compatibility. */ @@ -3736,6 +3823,13 @@ public final class AutofillManager { new FillCallback(callback, id)); } } + + public void notifyFillDialogTriggerIds(List<AutofillId> ids) { + final AutofillManager afm = mAfm.get(); + if (afm != null) { + afm.post(() -> afm.setFillDialogTriggerIds(ids)); + } + } } private static final class AugmentedAutofillManagerClient diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl index 64507aac54cb..2e5967cc32d1 100644 --- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl +++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl @@ -148,4 +148,9 @@ oneway interface IAutoFillManagerClient { */ void requestFillFromClient(int id, in InlineSuggestionsRequest request, in IFillCallback callback); + + /** + * Notifies autofill ids that require to show the fill dialog. + */ + void notifyFillDialogTriggerIds(in List<AutofillId> ids); } 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..7c939aca09cf 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. diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index fd1e84822193..3fa62e017976 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -599,6 +599,7 @@ public final class TransitionInfo implements Parcelable { private final Rect mTransitionBounds = new Rect(); private HardwareBuffer mThumbnail; private int mAnimations; + private @ColorInt int mBackgroundColor; private AnimationOptions(int type) { mType = type; @@ -608,6 +609,7 @@ public final class TransitionInfo implements Parcelable { mType = in.readInt(); mEnterResId = in.readInt(); mExitResId = in.readInt(); + mBackgroundColor = in.readInt(); mOverrideTaskTransition = in.readBoolean(); mPackageName = in.readString(); mTransitionBounds.readFromParcel(in); @@ -624,11 +626,12 @@ public final class TransitionInfo implements Parcelable { } public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId, - int exitResId, boolean overrideTaskTransition) { + int exitResId, @ColorInt int backgroundColor, boolean overrideTaskTransition) { AnimationOptions options = new AnimationOptions(ANIM_CUSTOM); options.mPackageName = packageName; options.mEnterResId = enterResId; options.mExitResId = exitResId; + options.mBackgroundColor = backgroundColor; options.mOverrideTaskTransition = overrideTaskTransition; return options; } @@ -673,6 +676,10 @@ public final class TransitionInfo implements Parcelable { return mExitResId; } + public @ColorInt int getBackgroundColor() { + return mBackgroundColor; + } + public boolean getOverrideTaskTransition() { return mOverrideTaskTransition; } @@ -698,6 +705,7 @@ public final class TransitionInfo implements Parcelable { dest.writeInt(mType); dest.writeInt(mEnterResId); dest.writeInt(mExitResId); + dest.writeInt(mBackgroundColor); dest.writeBoolean(mOverrideTaskTransition); dest.writeString(mPackageName); mTransitionBounds.writeToParcel(dest, flags); @@ -740,7 +748,7 @@ public final class TransitionInfo implements Parcelable { @Override public String toString() { - return "{ AnimationOtions type= " + typeToString(mType) + " package=" + mPackageName + return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}"; } } 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/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 25ee2d0fa019..cbd593207166 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]; @@ -5886,6 +6003,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 +6428,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 +6530,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 +6550,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 +7963,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); @@ -12553,6 +12802,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 +16361,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) { 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/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/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java index bfe43237da58..17b84ffc2f3f 100644 --- a/core/java/com/android/internal/util/UserIcons.java +++ b/core/java/com/android/internal/util/UserIcons.java @@ -16,6 +16,7 @@ package com.android.internal.util; +import android.annotation.ColorInt; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -72,9 +73,30 @@ public class UserIcons { // Return colored icon instead colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length]; } + return getDefaultUserIconInColor(resources, resources.getColor(colorResId, null)); + } + + /** + * Returns a default user icon in a particular color. + * + * @param resources resources object to fetch the user icon + * @param color the color used for the icon + */ + public static Drawable getDefaultUserIconInColor(Resources resources, @ColorInt int color) { Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate(); - icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN); + icon.setColorFilter(color, Mode.SRC_IN); icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); return icon; } + + /** + * Returns an array containing colors to be used for default user icons. + */ + public static int[] getUserIconColors(Resources resources) { + int[] result = new int[USER_ICON_COLORS.length]; + for (int i = 0; i < result.length; i++) { + result[i] = resources.getColor(USER_ICON_COLORS[i], null); + } + return result; + } } 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/OWNERS b/core/jni/OWNERS index 9a460f58effe..24c0d2a9795e 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -66,6 +66,7 @@ per-file com_android_internal_net_* = file:/services/core/java/com/android/serve ### Graphics ### per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS +per-file android_hardware_HardwareBuffer.cpp = file:/graphics/java/android/graphics/OWNERS ### Text ### per-file android_text_* = file:/core/java/android/text/OWNERS 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_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp index b25b4f766b89..f462523c6ce4 100644 --- a/core/jni/android_hardware_HardwareBuffer.cpp +++ b/core/jni/android_hardware_HardwareBuffer.cpp @@ -85,7 +85,7 @@ static jlong android_hardware_HardwareBuffer_create(JNIEnv* env, jobject clazz, sp<GraphicBuffer> buffer = new GraphicBuffer(width, height, pixelFormat, layers, grallocUsage, std::string("HardwareBuffer pid [") + std::to_string(getpid()) +"]"); status_t error = buffer->initCheck(); - if (error < 0) { + if (error != OK) { if (kDebugGraphicBuffer) { ALOGW("createGraphicBuffer() failed in HardwareBuffer.create()"); } 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 1be18733e97d..000000000000 --- a/core/jni/android_server_NetworkManagementSocketTagger.cpp +++ /dev/null @@ -1,91 +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 jint setCounterSet(JNIEnv* env, jclass, jint setNum, jint uid) { - int res = qtaguid_setCounterSet(setNum, uid); - if (res < 0) { - return (jint)-errno; - } - return (jint)res; -} - -static jint deleteTagData(JNIEnv* env, jclass, jint tagNum, jint uid) { - int res = qtaguid_deleteTagData(tagNum, uid); - 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}, - { "native_setCounterSet", "(II)I", (void*)setCounterSet}, - { "native_deleteTagData", "(II)I", (void*)deleteTagData}, -}; - -int register_android_server_NetworkManagementSocketTagger(JNIEnv* env) { - return jniRegisterNativeMethods(env, "com/android/server/NetworkManagementSocketTagger", gQTagUidMethods, NELEM(gQTagUidMethods)); -} - -}; diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto index acb7429df5c3..d48ea3b8785c 100644 --- a/core/proto/android/server/powermanagerservice.proto +++ b/core/proto/android/server/powermanagerservice.proto @@ -127,7 +127,7 @@ message PowerManagerServiceDumpProto { optional bool is_light_device_idle_mode = 25; // True if we are currently in device idle mode. optional bool is_device_idle_mode = 26; - // Set of app ids that we will always respect the wake locks for. + // Set of app ids that we will respect the wake locks for while in device idle mode. repeated int32 device_idle_whitelist = 27; // Set of app ids that are temporarily allowed to acquire wakelocks due to // high-pri message @@ -187,6 +187,8 @@ message PowerManagerServiceDumpProto { // Whether or not the current enhanced discharge prediction is personalized based on device // usage or not. optional bool is_enhanced_discharge_prediction_personalized = 54; + optional bool is_low_power_standby_active = 55; + optional LowPowerStandbyControllerDumpProto low_power_standby_controller = 56; } // A com.android.server.power.PowerManagerService.SuspendBlockerImpl object. @@ -209,6 +211,8 @@ message WakeLockProto { // When this wake lock is released, poke the user activity timer // so the screen stays on for a little longer. optional bool is_on_after_release = 2; + // The wakelock is held by the system server on request by another app. + optional bool system_wakelock = 3; } optional .android.os.WakeLockLevelEnum lock_level = 1; @@ -428,3 +432,39 @@ message BatterySaverStateMachineProto { // Next tag: 23 } + +message LowPowerStandbyControllerDumpProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + + // True if Low Power Standby is active + optional bool is_active = 1; + + // True if Low Power Standby is enabled + optional bool is_enabled = 2; + + // True if Low Power Standby is supported + optional bool is_supported_config = 3; + + // True if Low Power Standby is enabled by default + optional bool is_enabled_by_default_config = 4; + + // True if the device is currently interactive + optional bool is_interactive = 5; + + // Time (in elapsedRealtime) when the device was last interactive + optional bool last_interactive_time = 6; + + // Time (in milliseconds) after becoming non-interactive that Low Power Standby can activate + optional int32 standby_timeout_config = 7; + + // True if the device has entered idle mode since becoming non-interactive + optional int32 idle_since_non_interactive = 8; + + // True if the device is currently in idle mode + optional int32 is_device_idle = 9; + + // Set of app ids that are exempt form low power standby + repeated int32 allowlist = 10; + + // Next tag: 11 +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 0a94fa3ae9d3..7df9ca73b0d5 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -107,6 +107,7 @@ <protected-broadcast android:name="android.os.action.POWER_SAVE_WHITELIST_CHANGED" /> <protected-broadcast android:name="android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED" /> <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" /> + <protected-broadcast android:name="android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED" /> <protected-broadcast android:name="android.os.action.ENHANCED_DISCHARGE_PREDICTION_CHANGED" /> <!-- @deprecated This is rarely used and will be phased out soon. --> @@ -718,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 --> @@ -1143,6 +1145,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 --> <!-- ====================================================================== --> @@ -1838,11 +1849,12 @@ <permission android:name="android.permission.ACCESS_MOCK_LOCATION" android:protectionLevel="signature" /> - <!-- @SystemApi @hide Allows automotive applications to control location + <!-- @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) + Allows automotive applications to control location suspend state for power management use cases. <p>Not for use by third-party applications. --> - <permission android:name="android.permission.AUTOMOTIVE_GNSS_CONTROLS" + <permission android:name="android.permission.CONTROL_AUTOMOTIVE_GNSS" android:protectionLevel="signature|privileged" /> <!-- ======================================= --> @@ -3661,6 +3673,13 @@ <permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES" android:protectionLevel="signature|privileged|development" /> + <!-- @hide @SystemApi Must be required by a + {@link com.android.service.tracing.TraceReportService}, to ensure that only the system + can bind to it. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.BIND_TRACE_REPORT_SERVICE" + android:protectionLevel="signature" /> + <!-- @hide @SystemApi @TestApi Allow an application to approve incident and bug reports to be shared off-device. There can be only one application installed on the @@ -4955,6 +4974,11 @@ <permission android:name="android.permission.USER_ACTIVITY" android:protectionLevel="signature|privileged" /> + <!-- @hide @SystemApi Allows an application to manage Low Power Standby settings. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY" + android:protectionLevel="signature|privileged" /> + <!-- @hide Allows low-level access to tun tap driver --> <permission android:name="android.permission.NET_TUNNELING" android:protectionLevel="signature" /> @@ -5402,7 +5426,7 @@ android:protectionLevel="signature|setup|role" /> <!-- Allows access to keyguard secure storage. Only allowed for system processes. - @hide --> + @hide @TestApi --> <permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" android:protectionLevel="signature|setup" /> @@ -6282,6 +6306,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. --> @@ -6776,10 +6809,6 @@ android:resource="@xml/autofill_compat_accessibility_service" /> </service> - <service android:name="com.google.android.startop.iorap.IorapForwardingService$IorapdJobServiceProxy" - android:permission="android.permission.BIND_JOB_SERVICE" > - </service> - <service android:name="com.android.server.blob.BlobStoreIdleJobService" android:permission="android.permission.BIND_JOB_SERVICE"> </service> diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml new file mode 100644 index 000000000000..cce9593f1f2a --- /dev/null +++ b/core/res/res/layout/autofill_fill_dialog.xml @@ -0,0 +1,115 @@ +<?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. +--> + +<!-- NOTE: outer layout is required to provide proper shadow. --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/autofill_dialog_picker" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/autofill_save_outer_top_margin" + android:padding="@dimen/autofill_save_outer_top_padding" + android:elevation="@dimen/autofill_elevation" + android:background="?android:attr/colorBackground" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:paddingStart="@dimen/autofill_save_inner_padding" + android:paddingEnd="@dimen/autofill_save_inner_padding" + android:orientation="vertical"> + + <ImageView + android:id="@+id/autofill_service_icon" + android:scaleType="fitStart" + android:visibility="gone" + android:layout_width="@dimen/autofill_dialog_icon_size" + android:layout_height="@dimen/autofill_dialog_icon_size"/> + + <LinearLayout + android:id="@+id/autofill_dialog_header" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:paddingStart="@dimen/autofill_save_inner_padding" + android:paddingEnd="@dimen/autofill_save_inner_padding" + android:visibility="gone" + android:foreground="?attr/listChoiceBackgroundIndicator" + style="@style/AutofillDatasetPicker" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/autofill_dialog_container" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:paddingStart="@dimen/autofill_save_inner_padding" + android:paddingEnd="@dimen/autofill_save_inner_padding" + android:visibility="gone" + android:foreground="?attr/listChoiceBackgroundIndicator" + style="@style/AutofillDatasetPicker" /> + + <ListView + android:id="@+id/autofill_dialog_list" + android:layout_weight="1" + android:layout_width="fill_parent" + android:layout_height="0dp" + android:drawSelectorOnTop="true" + android:clickable="true" + android:divider="@null" + android:visibility="gone" + android:paddingStart="@dimen/autofill_save_inner_padding" + android:paddingEnd="@dimen/autofill_save_inner_padding" + android:foreground="?attr/listChoiceBackgroundIndicator" + style="@style/AutofillDatasetPicker" /> + + <com.android.internal.widget.ButtonBarLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:padding="@dimen/autofill_save_button_bar_padding" + android:clipToPadding="false" + android:orientation="horizontal"> + + <Button + android:id="@+id/autofill_dialog_no" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="?android:attr/buttonBarButtonStyle" + android:text="@string/dismiss_action"> + </Button> + + <Space + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_weight="1" + android:visibility="invisible"> + </Space> + + <Button + android:id="@+id/autofill_dialog_yes" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/Widget.DeviceDefault.Button.Colored" + android:text="@string/autofill_save_yes" + android:visibility="gone" > + </Button> + + </com.android.internal.widget.ButtonBarLayout> + +</LinearLayout> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 74cd519db169..1997e91a579e 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: @@ -5133,6 +5140,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> @@ -5624,4 +5634,14 @@ <!-- Whether or not to enable the lock screen entry point for the QR code scanner. --> <bool name="config_enableQrCodeScannerOnLockScreen">false</bool> + + <!-- Whether Low Power Standby is supported and can be enabled. --> + <bool name="config_lowPowerStandbySupported">false</bool> + + <!-- If supported, whether Low Power Standby is enabled by default. --> + <bool name="config_lowPowerStandbyEnabledByDefault">false</bool> + + <!-- The amount of time after becoming non-interactive (in ms) after which + Low Power Standby can activate. --> + <integer name="config_lowPowerStandbyNonInteractiveTimeout">5000</integer> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 771c0724fb01..4874e6529620 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -877,6 +877,9 @@ <!-- Maximum number of datasets that are visible in the UX picker without scrolling --> <integer name="autofill_max_visible_datasets">3</integer> + <!-- Size of an icon in the Autolfill fill dialog --> + <dimen name="autofill_dialog_icon_size">56dp</dimen> + <!-- Size of a slice shortcut view --> <dimen name="slice_shortcut_size">56dp</dimen> <!-- Size of action icons in a slice --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8d51dbeec690..1884467cdcac 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -465,6 +465,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 +473,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" /> @@ -3510,6 +3512,7 @@ <java-symbol type="layout" name="autofill_dataset_picker"/> <java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/> <java-symbol type="layout" name="autofill_dataset_picker_header_footer"/> + <java-symbol type="layout" name="autofill_fill_dialog"/> <java-symbol type="id" name="autofill" /> <java-symbol type="id" name="autofill_dataset_footer"/> <java-symbol type="id" name="autofill_dataset_header"/> @@ -3522,6 +3525,13 @@ <java-symbol type="id" name="autofill_save_no" /> <java-symbol type="id" name="autofill_save_title" /> <java-symbol type="id" name="autofill_save_yes" /> + <java-symbol type="id" name="autofill_service_icon" /> + <java-symbol type="id" name="autofill_dialog_picker"/> + <java-symbol type="id" name="autofill_dialog_header"/> + <java-symbol type="id" name="autofill_dialog_container"/> + <java-symbol type="id" name="autofill_dialog_list"/> + <java-symbol type="id" name="autofill_dialog_no" /> + <java-symbol type="id" name="autofill_dialog_yes" /> <java-symbol type="string" name="autofill_error_cannot_autofill" /> <java-symbol type="string" name="autofill_picker_no_suggestions" /> <java-symbol type="string" name="autofill_picker_some_suggestions" /> @@ -4320,6 +4330,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" /> @@ -4672,4 +4683,8 @@ <java-symbol type="string" name="notification_content_abusive_bg_apps"/> <java-symbol type="string" name="notification_content_long_running_fgs"/> <java-symbol type="string" name="notification_action_check_bg_apps"/> + + <java-symbol type="bool" name="config_lowPowerStandbySupported" /> + <java-symbol type="bool" name="config_lowPowerStandbyEnabledByDefault" /> + <java-symbol type="integer" name="config_lowPowerStandbyNonInteractiveTimeout" /> </resources> 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/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS index 74cdd2146937..10f6f1fd22f1 100644 --- a/core/tests/coretests/src/android/view/OWNERS +++ b/core/tests/coretests/src/android/view/OWNERS @@ -16,3 +16,6 @@ per-file *Window* = file:/services/core/java/com/android/server/wm/OWNERS # Scroll Capture per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS + +# Stylus +per-file stylus/* = file:/core/java/android/text/OWNERS
\ No newline at end of file diff --git a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index 632a1a9384e3..e11fe17bac9f 100644 --- a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view; +package android.view.stylus; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; @@ -33,6 +33,12 @@ import android.app.Instrumentation; import android.content.Context; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.view.HandwritingInitiator; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -47,7 +53,7 @@ import org.junit.runner.RunWith; * Tests for {@link HandwritingInitiator} * * Build/Install/Run: - * atest FrameworksCoreTests:HandwritingInitiatorTest + * atest FrameworksCoreTests:android.view.stylus.HandwritingInitiatorTest */ @Presubmit @SmallTest 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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index e68b1ace4c2a..b3dcc34d6e93 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -334,6 +334,7 @@ applications that come with the platform <permission name="android.permission.MANAGE_ACCESSIBILITY"/> <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> <permission name="android.permission.MANAGE_GAME_MODE"/> + <permission name="android.permission.MANAGE_LOW_POWER_STANDBY" /> <permission name="android.permission.MANAGE_ROLLBACKS"/> <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/> 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/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml index 84cf285a4b32..76b40361545e 100644 --- a/libs/WindowManager/Shell/res/values-te/strings.xml +++ b/libs/WindowManager/Shell/res/values-te/strings.xml @@ -36,16 +36,16 @@ <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."</string> <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string> <string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string> - <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు పూర్తి స్క్రీన్"</string> + <string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"ఎడమవైపు ఫుల్-స్క్రీన్"</string> <string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"ఎడమవైపు 70%"</string> <string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"ఎడమవైపు 50%"</string> <string name="accessibility_action_divider_left_30" msgid="6023611335723838727">"ఎడమవైపు 30%"</string> - <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు పూర్తి స్క్రీన్"</string> - <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ పూర్తి స్క్రీన్"</string> + <string name="accessibility_action_divider_right_full" msgid="3408505054325944903">"కుడివైపు ఫుల్-స్క్రీన్"</string> + <string name="accessibility_action_divider_top_full" msgid="3495871951082107594">"ఎగువ ఫుల్-స్క్రీన్"</string> <string name="accessibility_action_divider_top_70" msgid="1779164068887875474">"ఎగువ 70%"</string> <string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string> <string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ఎగువ 30%"</string> - <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ పూర్తి స్క్రీన్"</string> + <string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ ఫుల్-స్క్రీన్"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"వన్-హ్యాండెడ్ మోడ్ను ఉపయోగించడం"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"వన్-హ్యాండెడ్ మోడ్ను ప్రారంభిస్తుంది"</string> 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/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..d44db498451e 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; @@ -156,10 +155,6 @@ public class DragLayout extends LinearLayout { } } - public boolean hasDropTarget() { - return mCurrentTarget != null; - } - public boolean hasDropped() { return mHasDropped; } @@ -271,6 +266,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 +284,8 @@ public class DragLayout extends LinearLayout { animateHighlight(target); } else { // Switching between targets - animateHighlight(target); + mDropZoneView1.animateSwitch(); + mDropZoneView2.animateSwitch(); } mCurrentTarget = target; } @@ -323,7 +322,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 +342,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/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 13e81bdb3c0b..79c8a87acb5b 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 @@ -371,10 +371,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } if (a.getShowBackground()) { - // use the window's background color if provided as the background color for the - // animation - the top most window with a valid background color and - // showBackground set takes precedence. - if (change.getBackgroundColor() != 0) { + if (info.getAnimationOptions().getBackgroundColor() != 0) { + // If available use the background color provided through AnimationOptions + backgroundColorForTransition = + info.getAnimationOptions().getBackgroundColor(); + } else if (change.getBackgroundColor() != 0) { + // Otherwise default to the window's background color if provided through + // the theme as the background color for the animation - the top most window + // with a valid background color and showBackground set takes precedence. backgroundColorForTransition = change.getBackgroundColor(); } } 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/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/libs/tracingproxy/Android.bp b/libs/tracingproxy/Android.bp index 7126bfac773d..23d107b56340 100644 --- a/libs/tracingproxy/Android.bp +++ b/libs/tracingproxy/Android.bp @@ -37,6 +37,7 @@ cc_library_shared { srcs: [ ":ITracingServiceProxy.aidl", + ":TraceReportParams.aidl", ], shared_libs: [ diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 8054fd452022..f4e965f422c0 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -125,8 +125,8 @@ interface ILocationManager boolean isAdasGnssLocationEnabledForUser(int userId); void setAdasGnssLocationEnabledForUser(boolean enabled, int userId); - boolean isAutoGnssSuspended(); - void setAutoGnssSuspended(boolean suspended); + boolean isAutomotiveGnssSuspended(); + void setAutomotiveGnssSuspended(boolean suspended); void addTestProvider(String name, in ProviderProperties properties, in List<String> locationTags, String packageName, @nullable String attributionTag); 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 9109a18f120e..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()); @@ -758,45 +760,41 @@ public class LocationManager { } /** - * Set whether GNSS requests are suspended on the device. + * Set whether GNSS requests are suspended on the automotive device. * - * This method was added to help support power management use cases on automotive devices. More - * specifically, it is being added to fix a suspend to RAM issue where the SoC can't go into - * a lower power state when applications are actively requesting GNSS updates. + * For devices where GNSS prevents the system from going into a low power state, GNSS should + * be suspended right before going into the lower power state and resumed right after the device + * wakes up. * - * Ideally, the issue should be fixed at a lower layer in the stack, but this API introduces a - * workaround in the platform layer. This API allows car specific services to halt GNSS requests - * based on changes to the car power policy, which will in turn enable the device to go into - * suspend. + * This method disables GNSS and should only be used for power management use cases such as + * suspend-to-RAM or suspend-to-disk. * * @hide */ - @SystemApi - @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS) - public void setAutoGnssSuspended(boolean suspended) { + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE) + @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) + public void setAutomotiveGnssSuspended(boolean suspended) { try { - mService.setAutoGnssSuspended(suspended); + mService.setAutomotiveGnssSuspended(suspended); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Return whether GNSS requests are suspended or not. - * - * This method was added to help support power management use cases on automotive devices. More - * specifically, it is being added as part of the fix for a suspend to RAM issue where the SoC - * can't go into a lower power state when applications are actively requesting GNSS updates. + * Return whether GNSS requests are suspended on the automotive device. * * @return true if GNSS requests are suspended and false if they aren't. * * @hide */ - @SystemApi - @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS) - public boolean isAutoGnssSuspended() { + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE) + @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) + public boolean isAutomotiveGnssSuspended() { try { - return mService.isAutoGnssSuspended(); + return mService.isAutomotiveGnssSuspended(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } 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..1b4c0bc64f78 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) */ 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..06dc498afe5e 100644 --- a/media/java/android/media/RouteDiscoveryPreference.java +++ b/media/java/android/media/RouteDiscoveryPreference.java @@ -64,6 +64,10 @@ public final class RouteDiscoveryPreference implements Parcelable { @NonNull private final List<String> mPreferredFeatures; + private final List<String> mRequiredFeatures; + private final List<String> mPackagesOrder; + private final List<String> mAllowedPackages; + private final boolean mShouldPerformActiveScan; @Nullable private final Bundle mExtras; @@ -78,12 +82,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 +106,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 +115,44 @@ 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. + * This precedes {@link #getPreferredFeatures()}. + * 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> + * When the app enables duplicate removal, all the routes that have the same deduplication ID + * except one from the provider whose package name appears first in the list will be removed. + */ + @NonNull + public List<String> getDeduplicationPackageOrder() { + return mPackagesOrder == null ? List.of() : 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 +164,16 @@ public final class RouteDiscoveryPreference implements Parcelable { } /** + * Gets whether duplicate routes should be removed. + * <p> + * If it is {@code true}, only one of routes that have + * the same deduplication ID will be obtained. + */ + public boolean shouldRemoveDuplicates() { + return !mPackagesOrder.isEmpty(); + } + + /** * @hide */ public Bundle getExtras() { @@ -128,6 +188,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 +218,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 +236,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 +258,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 +301,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 +338,27 @@ public final class RouteDiscoveryPreference implements Parcelable { } /** + * Sets the order of packages when removing duplicate routes. + * <p> + * Routes are removed based on its + * {@link MediaRoute2Info#getDeduplicationIds() deduplication ID}. + * If two routes have the same ID, even if they are from different providers, + * one of them is removed from the list. + * <p> + * Routes from the provider whose package name appears first in the given package order + * will remain. + * If unspecified, any route can be selected. + * + * @param packageOrder list of package names for choosing routes to be removed or + * {@code null} not to remove duplicate routes. + */ + @NonNull + public Builder setDeduplicationPackageOrder(@Nullable List<String> packageOrder) { + mPackageOrder = (packageOrder == null) ? null : List.copyOf(packageOrder); + return this; + } + + /** * Sets the extras of the route. * @hide */ diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 41666c7225d5..75236f455057 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -1851,6 +1851,7 @@ public final class TvInputManager { * * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) public int getClientPid(@NonNull String sessionId) { return getClientPidInternal(sessionId); diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index ffc443306df5..1e32cadc2748 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -68,6 +68,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -1602,25 +1603,27 @@ public class Tuner implements AutoCloseable { * * @param statusTypes an array of status types. * - * @return an array of current readiness states. {@code null} if the operation failed or - * unsupported versions. + * @return a list of current readiness states. It is empty if the operation fails or unsupported + * versions. * @throws IllegalStateException if there is no active frontend currently. */ - @Nullable - @SuppressLint("ArrayReturn") - @SuppressWarnings("NullableCollection") - public FrontendStatusReadiness[] getFrontendStatusReadiness( + @NonNull + public List<FrontendStatusReadiness> getFrontendStatusReadiness( @NonNull @FrontendStatusType int[] statusTypes) { mFrontendLock.lock(); try { if (!TunerVersionChecker.checkHigherOrEqualVersionTo( - TunerVersionChecker.TUNER_VERSION_2_0, "Remove output PID")) { - return null; + TunerVersionChecker.TUNER_VERSION_2_0, "Get fronted status readiness")) { + return Collections.EMPTY_LIST; } if (mFrontend == null) { throw new IllegalStateException("frontend is not initialized"); } - return nativeGetFrontendStatusReadiness(statusTypes); + FrontendStatusReadiness[] readiness = nativeGetFrontendStatusReadiness(statusTypes); + if (readiness == null) { + return Collections.EMPTY_LIST; + } + return Arrays.asList(readiness); } finally { mFrontendLock.unlock(); } diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java index 52527b3fb065..c30753ccc3a0 100644 --- a/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java +++ b/media/java/android/media/tv/tuner/frontend/FrontendStatusReadiness.java @@ -30,7 +30,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi -public class FrontendStatusReadiness { +public final class FrontendStatusReadiness { /** @hide */ @IntDef({FRONTEND_STATUS_READINESS_UNDEFINED, FRONTEND_STATUS_READINESS_UNAVAILABLE, FRONTEND_STATUS_READINESS_UNSTABLE, FRONTEND_STATUS_READINESS_STABLE, diff --git a/media/jni/Android.bp b/media/jni/Android.bp index feae6065c680..18b779fa7c57 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -87,6 +87,7 @@ cc_library_shared { "android.hardware.drm@1.4", "android.hidl.memory@1.0", "android.hidl.token@1.0-utils", + "android.hardware.drm-V1-ndk", ], header_libs: [ 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/omapi/OWNERS b/omapi/OWNERS new file mode 100644 index 000000000000..5682fd3281f4 --- /dev/null +++ b/omapi/OWNERS @@ -0,0 +1,5 @@ +# Bug component: 456592 + +zachoverflow@google.com +alisher@google.com +jackcwyu@google.com 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/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 index 1eb52fb44629..8aca1d67942f 100644 --- a/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java +++ b/packages/ConnectivityT/framework-t/src/com/android/server/NetworkManagementSocketTagger.java @@ -111,36 +111,6 @@ public final class NetworkManagementSocketTagger extends SocketTagger { public int statsUid = -1; } - public static void setKernelCounterSet(int uid, int counterSet) { - final int errno = native_setCounterSet(counterSet, uid); - if (errno < 0) { - Log.w(TAG, "setKernelCountSet(" + uid + ", " + counterSet + ") failed with errno " - + errno); - } - } - - public static void resetKernelUidStats(int uid) { - int errno = native_deleteTagData(0, uid); - if (errno < 0) { - Log.w(TAG, "problem clearing counters for uid " + uid + " : errno " + errno); - } - } - - /** - * 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); - private static native int native_setCounterSet(int uid, int counterSetNum); - private static native int native_deleteTagData(int tag, int uid); } 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 8e584d084fb9..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; @@ -59,7 +59,6 @@ import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport; import static com.android.net.module.util.NetworkStatsUtils.LIMIT_GLOBAL_ALERT; -import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats; import android.annotation.NonNull; import android.annotation.Nullable; @@ -121,6 +120,7 @@ import android.provider.Settings.Global; import android.service.NetworkInterfaceProto; import android.service.NetworkStatsServiceDumpProto; import android.system.ErrnoException; +import android.system.Os; import android.telephony.PhoneStateListener; import android.telephony.SubscriptionPlan; import android.text.TextUtils; @@ -546,6 +546,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return null; } } + + public TagStatsDeleter getTagStatsDeleter() { + return NetworkStatsService::nativeDeleteTagData; + } } /** @@ -1801,7 +1805,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // Clear kernel stats associated with UID for (int uid : uids) { - resetKernelUidStats(uid); + final int ret = mDeps.getTagStatsDeleter().deleteTagData(uid); + if (ret < 0) { + Log.w(TAG, "problem clearing counters for uid " + uid + ": " + Os.strerror(-ret)); + } } } @@ -2380,4 +2387,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private static native long nativeGetTotalStat(int type); private static native long nativeGetIfaceStat(String iface, int type); private static native long nativeGetUidStat(int uid, int type); + + // TODO: use BpfNetMaps to delete tag data and remove this. + @VisibleForTesting + interface TagStatsDeleter { + int deleteTagData(int uid); + } + + private static native int nativeDeleteTagData(int uid); } diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index fcf2282160a7..684f4dee9ac2 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -50,6 +50,7 @@ android_library { "SettingsLibSettingsTransition", "SettingsLibActivityEmbedding", "SettingsLibButtonPreference", + "setupdesign", ], // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml index a3473459948d..13f8a372c9b5 100644 --- a/packages/SettingsLib/AndroidManifest.xml +++ b/packages/SettingsLib/AndroidManifest.xml @@ -18,4 +18,10 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.settingslib"> + <application> + <activity + android:name="com.android.settingslib.users.AvatarPickerActivity" + android:theme="@style/SudThemeGlifV2.DayNight"/> + </application> + </manifest> diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java index e51bb45ebca2..3eb6ea98f118 100644 --- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java +++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java @@ -42,6 +42,7 @@ public class FooterPreference extends Preference { @VisibleForTesting View.OnClickListener mLearnMoreListener; private CharSequence mContentDescription; + private CharSequence mLearnMoreText; private CharSequence mLearnMoreContentDescription; private FooterLearnMoreSpan mLearnMoreSpan; @@ -69,7 +70,12 @@ public class FooterPreference extends Preference { TextView learnMore = holder.itemView.findViewById(R.id.settingslib_learn_more); if (learnMore != null && mLearnMoreListener != null) { learnMore.setVisibility(View.VISIBLE); - SpannableString learnMoreText = new SpannableString(learnMore.getText()); + if (TextUtils.isEmpty(mLearnMoreText)) { + mLearnMoreText = learnMore.getText(); + } else { + learnMore.setText(mLearnMoreText); + } + SpannableString learnMoreText = new SpannableString(mLearnMoreText); if (mLearnMoreSpan != null) { learnMoreText.removeSpan(mLearnMoreSpan); } @@ -123,6 +129,18 @@ public class FooterPreference extends Preference { } /** + * Sets the learn more text. + * + * @param learnMoreText The string of the learn more text. + */ + public void setLearnMoreText(CharSequence learnMoreText) { + if (!TextUtils.equals(mLearnMoreText, learnMoreText)) { + mLearnMoreText = learnMoreText; + notifyChanged(); + } + } + + /** * To set content description of the learn more text. This can use for talkback * environment if developer wants to have a customization content. * diff --git a/packages/SettingsLib/res/drawable/add_a_photo_circled.xml b/packages/SettingsLib/res/drawable/add_a_photo_circled.xml new file mode 100644 index 000000000000..bcfd221e94c5 --- /dev/null +++ b/packages/SettingsLib/res/drawable/add_a_photo_circled.xml @@ -0,0 +1,28 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item> + <shape android:shape="oval"> + <solid android:color="?android:attr/colorAccent"/> + </shape> + </item> + <item + android:left="@dimen/add_a_photo_circled_padding" + android:right="@dimen/add_a_photo_circled_padding" + android:top="@dimen/add_a_photo_circled_padding" + android:bottom="@dimen/add_a_photo_circled_padding" + android:drawable="@drawable/ic_add_a_photo"/> +</layer-list> diff --git a/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml new file mode 100644 index 000000000000..97aec740ea43 --- /dev/null +++ b/packages/SettingsLib/res/drawable/avatar_choose_photo_circled.xml @@ -0,0 +1,30 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item> + <shape android:shape="oval"> + <stroke + android:width="2dp" + android:color="?android:attr/colorPrimary"/> + </shape> + </item> + <item + android:left="@dimen/avatar_picker_icon_inset" + android:right="@dimen/avatar_picker_icon_inset" + android:top="@dimen/avatar_picker_icon_inset" + android:bottom="@dimen/avatar_picker_icon_inset" + android:drawable="@drawable/ic_avatar_choose_photo"/> +</layer-list> diff --git a/packages/SettingsLib/res/drawable/avatar_selector.xml b/packages/SettingsLib/res/drawable/avatar_selector.xml new file mode 100644 index 000000000000..ccde59763a4a --- /dev/null +++ b/packages/SettingsLib/res/drawable/avatar_selector.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true"> + <shape android:shape="oval"> + <stroke + android:color="?android:attr/colorPrimary" + android:width="@dimen/avatar_picker_padding"/> + </shape> + </item> +</selector>
\ No newline at end of file diff --git a/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml new file mode 100644 index 000000000000..7033aaeec911 --- /dev/null +++ b/packages/SettingsLib/res/drawable/avatar_take_photo_circled.xml @@ -0,0 +1,30 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item> + <shape android:shape="oval"> + <stroke + android:width="2dp" + android:color="?android:attr/colorPrimary"/> + </shape> + </item> + <item + android:left="@dimen/avatar_picker_icon_inset" + android:right="@dimen/avatar_picker_icon_inset" + android:top="@dimen/avatar_picker_icon_inset" + android:bottom="@dimen/avatar_picker_icon_inset" + android:drawable="@drawable/ic_avatar_take_photo"/> +</layer-list> diff --git a/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml new file mode 100644 index 000000000000..0cc54b6b4972 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml @@ -0,0 +1,25 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="?android:attr/colorPrimary" + android:pathData="M5.85,17.1q1.275,-0.975 2.85,-1.538Q10.275,15 12,15q1.725,0 3.3,0.563 1.575,0.562 2.85,1.537 0.875,-1.025 1.363,-2.325Q20,13.475 20,12q0,-3.325 -2.337,-5.662Q15.325,4 12,4T6.338,6.338Q4,8.675 4,12q0,1.475 0.487,2.775 0.488,1.3 1.363,2.325zM12,13q-1.475,0 -2.488,-1.012Q8.5,10.975 8.5,9.5t1.012,-2.487Q10.525,6 12,6t2.488,1.013Q15.5,8.024 15.5,9.5t-1.012,2.488Q13.475,13 12,13zM12,22q-2.075,0 -3.9,-0.788 -1.825,-0.787 -3.175,-2.137 -1.35,-1.35 -2.137,-3.175Q2,14.075 2,12t0.788,-3.9q0.787,-1.825 2.137,-3.175 1.35,-1.35 3.175,-2.137Q9.925,2 12,2t3.9,0.788q1.825,0.787 3.175,2.137 1.35,1.35 2.137,3.175Q22,9.925 22,12t-0.788,3.9q-0.787,1.825 -2.137,3.175 -1.35,1.35 -3.175,2.137Q14.075,22 12,22zM12,20q1.325,0 2.5,-0.387 1.175,-0.388 2.15,-1.113 -0.975,-0.725 -2.15,-1.113Q13.325,17 12,17t-2.5,0.387q-1.175,0.388 -2.15,1.113 0.975,0.725 2.15,1.113Q10.675,20 12,20zM12,11q0.65,0 1.075,-0.425 0.425,-0.425 0.425,-1.075 0,-0.65 -0.425,-1.075Q12.65,8 12,8q-0.65,0 -1.075,0.425Q10.5,8.85 10.5,9.5q0,0.65 0.425,1.075Q11.35,11 12,11zM12,9.5zM12,18.5z"/> +</vector> + diff --git a/packages/SettingsLib/res/drawable/ic_add_a_photo.xml b/packages/SettingsLib/res/drawable/ic_add_a_photo.xml new file mode 100644 index 000000000000..4e355031befe --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_add_a_photo.xml @@ -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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="?android:attr/colorPrimary" + android:pathData="M11.5,17.5q1.875,0 3.188,-1.313Q16,14.876 16,13q0,-1.875 -1.313,-3.188Q13.376,8.5 11.5,8.5q-1.875,0 -3.188,1.313Q7,11.124 7,13q0,1.875 1.313,3.188Q9.624,17.5 11.5,17.5zM3.5,21q-0.825,0 -1.413,-0.587Q1.5,19.825 1.5,19L1.5,7q0,-0.825 0.587,-1.412Q2.675,5 3.5,5h3.15L8.5,3h6v4h-11v12h16v-9h2v9q0,0.825 -0.587,1.413Q20.325,21 19.5,21zM18.5,8L18.5,6h-2L16.5,4h2L18.5,2h2v2h2v2h-2v2zM11.5,13z"/> +</vector> diff --git a/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml new file mode 100644 index 000000000000..b85fdc2de597 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_avatar_choose_photo.xml @@ -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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="?android:attr/colorPrimary" + 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> diff --git a/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml new file mode 100644 index 000000000000..5c56276ec4f3 --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_avatar_take_photo.xml @@ -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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="?android:attr/colorPrimary" + android:pathData="M12,17.5q1.875,0 3.188,-1.313Q16.5,14.876 16.5,13q0,-1.875 -1.313,-3.188Q13.876,8.5 12,8.5q-1.875,0 -3.188,1.313Q7.5,11.124 7.5,13q0,1.875 1.313,3.188Q10.124,17.5 12,17.5zM4,21q-0.825,0 -1.413,-0.587Q2,19.825 2,19L2,7q0,-0.825 0.587,-1.412Q3.175,5 4,5h3.15L9,3h6l1.85,2L20,5q0.825,0 1.413,0.588Q22,6.175 22,7v12q0,0.825 -0.587,1.413Q20.825,21 20,21zM20,19L20,7L4,7v12zM4,19L4,7v12z"/> +</vector> diff --git a/packages/SettingsLib/res/layout/avatar_item.xml b/packages/SettingsLib/res/layout/avatar_item.xml new file mode 100644 index 000000000000..c52f6648cb7e --- /dev/null +++ b/packages/SettingsLib/res/layout/avatar_item.xml @@ -0,0 +1,24 @@ +<?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. + --> +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/avatar_image" + android:layout_height="@dimen/avatar_size_in_picker" + android:layout_width="@dimen/avatar_size_in_picker" + android:layout_margin="@dimen/avatar_picker_margin" + android:layout_gravity="center" + android:padding="@dimen/avatar_picker_padding" + android:background="@drawable/avatar_selector"/> diff --git a/packages/SettingsLib/res/layout/avatar_picker.xml b/packages/SettingsLib/res/layout/avatar_picker.xml new file mode 100644 index 000000000000..2d40bd0de7ea --- /dev/null +++ b/packages/SettingsLib/res/layout/avatar_picker.xml @@ -0,0 +1,38 @@ +<?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. + --> +<com.google.android.setupdesign.GlifLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/glif_layout" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:icon="@drawable/ic_account_circle_outline" + app:sucUsePartnerResource="true" + app:sucHeaderText="@string/avatar_picker_title"> + + <LinearLayout + android:layout_height="match_parent" + android:layout_width="match_parent" + android:gravity="center_horizontal" + style="@style/SudContentFrame"> + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/avatar_grid" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </LinearLayout> + +</com.google.android.setupdesign.GlifLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml index f66ff007fb90..c8ddcc870e46 100644 --- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml +++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml @@ -18,24 +18,32 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:baselineAligned="false" - android:orientation="horizontal" + android:orientation="vertical" android:padding="16dp"> - <ImageView - android:id="@+id/user_photo" - android:layout_width="56dp" - android:layout_height="56dp" - android:layout_gravity="bottom" - android:contentDescription="@string/user_image_photo_selector" - android:background="@*android:drawable/spinner_background_holo_dark" - android:scaleType="fitCenter"/> + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"> + <ImageView + android:id="@+id/user_photo" + android:layout_width="@dimen/user_photo_size_in_profile_info_dialog" + android:layout_height="@dimen/user_photo_size_in_profile_info_dialog" + android:contentDescription="@string/user_image_photo_selector" + android:scaleType="fitCenter"/> + <ImageView + android:id="@+id/add_a_photo_icon" + android:layout_width="@dimen/add_a_photo_icon_size_in_profile_info_dialog" + android:layout_height="@dimen/add_a_photo_icon_size_in_profile_info_dialog" + android:src="@drawable/add_a_photo_circled" + android:layout_gravity="bottom|right" /> + </FrameLayout> <EditText android:id="@+id/user_name" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="bottom" - android:layout_weight="1" + android:layout_gravity="center" android:minWidth="200dp" android:layout_marginStart="6dp" android:minHeight="@dimen/min_tap_target_size" diff --git a/packages/SettingsLib/res/values-w1280dp-land/dimens.xml b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml new file mode 100644 index 000000000000..4097d0564251 --- /dev/null +++ b/packages/SettingsLib/res/values-w1280dp-land/dimens.xml @@ -0,0 +1,21 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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> + <integer name="avatar_picker_columns">4</integer> + <dimen name="avatar_size_in_picker">104dp</dimen> + <dimen name="avatar_picker_padding">8dp</dimen> + <dimen name="avatar_picker_margin">3dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values-w1440dp-land/dimens.xml b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml new file mode 100644 index 000000000000..764870efe222 --- /dev/null +++ b/packages/SettingsLib/res/values-w1440dp-land/dimens.xml @@ -0,0 +1,21 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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> + <integer name="avatar_picker_columns">4</integer> + <dimen name="avatar_size_in_picker">128dp</dimen> + <dimen name="avatar_picker_padding">8dp</dimen> + <dimen name="avatar_picker_margin">4dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values-w1600dp-land/dimens.xml b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml new file mode 100644 index 000000000000..872b88a3776d --- /dev/null +++ b/packages/SettingsLib/res/values-w1600dp-land/dimens.xml @@ -0,0 +1,21 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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> + <integer name="avatar_picker_columns">4</integer> + <dimen name="avatar_size_in_picker">148dp</dimen> + <dimen name="avatar_picker_padding">8dp</dimen> + <dimen name="avatar_picker_margin">6dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values-w480dp-port/dimens.xml b/packages/SettingsLib/res/values-w480dp-port/dimens.xml new file mode 100644 index 000000000000..cab78d64ce8d --- /dev/null +++ b/packages/SettingsLib/res/values-w480dp-port/dimens.xml @@ -0,0 +1,21 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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> + <integer name="avatar_picker_columns">3</integer> + <dimen name="avatar_size_in_picker">112dp</dimen> + <dimen name="avatar_picker_padding">8dp</dimen> + <dimen name="avatar_picker_margin">3dp</dimen> +</resources> diff --git a/packages/SettingsLib/res/values-w600dp-port/dimens.xml b/packages/SettingsLib/res/values-w600dp-port/dimens.xml new file mode 100644 index 000000000000..4097d0564251 --- /dev/null +++ b/packages/SettingsLib/res/values-w600dp-port/dimens.xml @@ -0,0 +1,21 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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> + <integer name="avatar_picker_columns">4</integer> + <dimen name="avatar_size_in_picker">104dp</dimen> + <dimen name="avatar_picker_padding">8dp</dimen> + <dimen name="avatar_picker_margin">3dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values-w720dp-port/dimens.xml b/packages/SettingsLib/res/values-w720dp-port/dimens.xml new file mode 100644 index 000000000000..764870efe222 --- /dev/null +++ b/packages/SettingsLib/res/values-w720dp-port/dimens.xml @@ -0,0 +1,21 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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> + <integer name="avatar_picker_columns">4</integer> + <dimen name="avatar_size_in_picker">128dp</dimen> + <dimen name="avatar_picker_padding">8dp</dimen> + <dimen name="avatar_picker_margin">4dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values-w840dp-port/dimens.xml b/packages/SettingsLib/res/values-w840dp-port/dimens.xml new file mode 100644 index 000000000000..872b88a3776d --- /dev/null +++ b/packages/SettingsLib/res/values-w840dp-port/dimens.xml @@ -0,0 +1,21 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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> + <integer name="avatar_picker_columns">4</integer> + <dimen name="avatar_size_in_picker">148dp</dimen> + <dimen name="avatar_picker_padding">8dp</dimen> + <dimen name="avatar_picker_margin">6dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values-w960dp-land/dimens.xml b/packages/SettingsLib/res/values-w960dp-land/dimens.xml new file mode 100644 index 000000000000..8403dba54329 --- /dev/null +++ b/packages/SettingsLib/res/values-w960dp-land/dimens.xml @@ -0,0 +1,21 @@ +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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> + <integer name="avatar_picker_columns">4</integer> + <dimen name="avatar_size_in_picker">96dp</dimen> + <dimen name="avatar_picker_padding">6dp</dimen> + <dimen name="avatar_picker_margin">2dp</dimen> +</resources> diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml index 2b5e9cdc017d..93e3deef7aa5 100644 --- a/packages/SettingsLib/res/values/arrays.xml +++ b/packages/SettingsLib/res/values/arrays.xml @@ -647,4 +647,6 @@ <item>disabled</item> </array> + <array name="avatar_images"/> + </resources> diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index 9bccc3f1d864..120df76218b3 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -98,4 +98,15 @@ <!-- Minimum width for the popup for updating a user's photo. --> <dimen name="update_user_photo_popup_min_width">300dp</dimen> + <dimen name="add_a_photo_circled_padding">6dp</dimen> + <dimen name="user_photo_size_in_profile_info_dialog">112dp</dimen> + <dimen name="add_a_photo_icon_size_in_profile_info_dialog">32dp</dimen> + + <integer name="avatar_picker_columns">3</integer> + <dimen name="avatar_size_in_picker">96dp</dimen> + <dimen name="avatar_picker_padding">6dp</dimen> + <dimen name="avatar_picker_margin">2dp</dimen> + + <dimen name="avatar_picker_icon_inset">25dp</dimen> + </resources> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index e4eab4b20109..af6a658f362a 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1164,6 +1164,9 @@ <!-- Summary for settings preference disabled by administrator [CHAR LIMIT=50] --> <string name="disabled_by_admin_summary_text">Controlled by admin</string> + <!-- Summary for settings preference disabled by app ops [CHAR LIMIT=50] --> + <string name="disabled_by_app_ops_text">Controlled by Restricted Setting</string> + <!-- [CHAR LIMIT=25] Manage applications, text telling using an application is disabled. --> <string name="disabled">Disabled</string> <!-- Summary of app trusted to install apps [CHAR LIMIT=45] --> @@ -1545,4 +1548,18 @@ <!-- 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> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index 1e8cb9fc4622..1573edbbfae9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -26,14 +26,17 @@ import android.app.AppGlobals; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.ForegroundColorSpan; @@ -42,6 +45,7 @@ import android.util.Log; import android.view.MenuItem; import android.widget.TextView; +import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; @@ -729,6 +733,26 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { } /** + * Show restricted setting dialog. + */ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + public static void sendShowRestrictedSettingDialogIntent(Context context, + String packageName, int uid) { + final Intent intent = getShowRestrictedSettingsIntent(packageName, uid); + context.startActivity(intent); + } + + /** + * Get restricted settings dialog intent. + */ + private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) { + final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(Intent.EXTRA_UID, uid); + return intent; + } + + /** * Static {@link LockPatternUtils} and {@link DevicePolicyManager} wrapper for testing purposes. * {@link LockPatternUtils} is an internal API not supported by robolectric. * {@link DevicePolicyManager} has a {@code getProfileParent} not yet suppored by robolectric. diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java index fc8b5879c5fa..81146fa9ffd1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java @@ -19,6 +19,7 @@ package com.android.settingslib; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.content.Context; +import android.os.Process; import android.os.UserHandle; import android.util.AttributeSet; import android.view.View; @@ -37,9 +38,14 @@ public class RestrictedPreference extends TwoTargetPreference { RestrictedPreferenceHelper mHelper; public RestrictedPreference(Context context, AttributeSet attrs, - int defStyleAttr, int defStyleRes) { + int defStyleAttr, int defStyleRes, String packageName, int uid) { super(context, attrs, defStyleAttr, defStyleRes); - mHelper = new RestrictedPreferenceHelper(context, this, attrs); + mHelper = new RestrictedPreferenceHelper(context, this, attrs, packageName, uid); + } + + public RestrictedPreference(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + this(context, attrs, defStyleAttr, defStyleRes, null, Process.INVALID_UID); } public RestrictedPreference(Context context, AttributeSet attrs, int defStyleAttr) { @@ -55,6 +61,11 @@ public class RestrictedPreference extends TwoTargetPreference { this(context, null); } + public RestrictedPreference(Context context, String packageName, int uid) { + this(context, null, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle, + android.R.attr.preferenceStyle), 0, packageName, uid); + } + @Override protected int getSecondTargetResId() { return R.layout.restricted_icon; @@ -115,7 +126,21 @@ public class RestrictedPreference extends TwoTargetPreference { } } + public void setDisabledByAppOps(boolean disabled) { + if (mHelper.setDisabledByAppOps(disabled)) { + notifyChanged(); + } + } + public boolean isDisabledByAdmin() { return mHelper.isDisabledByAdmin(); } + + public int getUid() { + return mHelper != null ? mHelper.uid : Process.INVALID_UID; + } + + public String getPackageName() { + return mHelper != null ? mHelper.packageName : null; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 83a6973ffec6..3f322d61e036 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -20,6 +20,7 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.content.Context; import android.content.res.TypedArray; +import android.os.Build; import android.os.UserHandle; import android.text.TextUtils; import android.util.AttributeSet; @@ -29,6 +30,8 @@ import android.widget.TextView; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; +import com.android.internal.util.Preconditions; + /** * Helper class for managing settings preferences that can be disabled * by device admins via user restrictions. @@ -36,16 +39,22 @@ import androidx.preference.PreferenceViewHolder; public class RestrictedPreferenceHelper { private final Context mContext; private final Preference mPreference; + final String packageName; + final int uid; private boolean mDisabledByAdmin; private EnforcedAdmin mEnforcedAdmin; private String mAttrUserRestriction = null; - private boolean mUseAdminDisabledSummary = false; + private boolean mDisabledSummary = false; + + private boolean mDisabledByAppOps; public RestrictedPreferenceHelper(Context context, Preference preference, - AttributeSet attrs) { + AttributeSet attrs, String packageName, int uid) { mContext = context; mPreference = preference; + this.packageName = packageName; + this.uid = uid; if (attrs != null) { final TypedArray attributes = context.obtainStyledAttributes(attrs, @@ -71,27 +80,34 @@ public class RestrictedPreferenceHelper { final TypedValue useAdminDisabledSummary = attributes.peekValue(R.styleable.RestrictedPreference_useAdminDisabledSummary); if (useAdminDisabledSummary != null) { - mUseAdminDisabledSummary = + mDisabledSummary = (useAdminDisabledSummary.type == TypedValue.TYPE_INT_BOOLEAN && useAdminDisabledSummary.data != 0); } } } + public RestrictedPreferenceHelper(Context context, Preference preference, + AttributeSet attrs) { + this(context, preference, attrs, null, android.os.Process.INVALID_UID); + } + /** * Modify PreferenceViewHolder to add padlock if restriction is disabled. */ public void onBindViewHolder(PreferenceViewHolder holder) { - if (mDisabledByAdmin) { + if (mDisabledByAdmin || mDisabledByAppOps) { holder.itemView.setEnabled(true); } - if (mUseAdminDisabledSummary) { + 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); if (mDisabledByAdmin) { summaryView.setText(disabledText); + } else if (mDisabledByAppOps) { + summaryView.setText(R.string.disabled_by_app_ops_text); } else if (TextUtils.equals(disabledText, summaryView.getText())) { // It's previously set to disabled text, clear it. summaryView.setText(null); @@ -101,7 +117,7 @@ public class RestrictedPreferenceHelper { } public void useAdminDisabledSummary(boolean useSummary) { - mUseAdminDisabledSummary = useSummary; + mDisabledSummary = useSummary; } /** @@ -109,11 +125,19 @@ public class RestrictedPreferenceHelper { * * @return true if the method handled the click. */ + @SuppressWarnings("NewApi") public boolean performClick() { if (mDisabledByAdmin) { RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin); return true; } + if (mDisabledByAppOps) { + Preconditions.checkState(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU, + "Build SDK version needs >= T"); + RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName, + uid); + return true; + } return false; } @@ -166,14 +190,34 @@ public class RestrictedPreferenceHelper { changed = true; } - if (!(mPreference instanceof RestrictedTopLevelPreference)) { - mPreference.setEnabled(!disabled); + updateDisabledState(); + + return changed; + } + + public boolean setDisabledByAppOps(boolean disabled) { + boolean changed = false; + if (mDisabledByAppOps != disabled) { + mDisabledByAppOps = disabled; + changed = true; } + updateDisabledState(); + return changed; } public boolean isDisabledByAdmin() { return mDisabledByAdmin; } + + public boolean isDisabledByAppOps() { + return mDisabledByAppOps; + } + + private void updateDisabledState() { + if (!(mPreference instanceof RestrictedTopLevelPreference)) { + mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); + } + } } 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/src/com/android/settingslib/users/AvatarPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java new file mode 100644 index 000000000000..61b8911acea4 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.users; + +import android.app.Activity; +import android.content.ClipData; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.media.ExifInterface; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.StrictMode; +import android.provider.ContactsContract; +import android.provider.MediaStore; +import android.util.EventLog; +import android.util.Log; + +import androidx.core.content.FileProvider; + +import libcore.io.Streams; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +class AvatarPhotoController { + private static final String TAG = "AvatarPhotoController"; + + private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001; + private static final int REQUEST_CODE_TAKE_PHOTO = 1002; + private static final int REQUEST_CODE_CROP_PHOTO = 1003; + // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI + // so we need a default photo size + private static final int DEFAULT_PHOTO_SIZE = 500; + + private static final String IMAGES_DIR = "multi_user"; + private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg"; + private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg"; + + private final int mPhotoSize; + + private final AvatarPickerActivity mActivity; + private final String mFileAuthority; + + private final File mImagesDir; + private final Uri mCropPictureUri; + private final Uri mTakePictureUri; + + AvatarPhotoController(AvatarPickerActivity activity, boolean waiting, String fileAuthority) { + mActivity = activity; + mFileAuthority = fileAuthority; + + mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR); + mImagesDir.mkdir(); + mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting); + mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting); + mPhotoSize = getPhotoSize(activity); + } + + /** + * Handles activity result from containing activity/fragment after a take/choose/crop photo + * action result is received. + */ + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK) { + return false; + } + final Uri pictureUri = data != null && data.getData() != null + ? data.getData() : mTakePictureUri; + + // Check if the result is a content uri + if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) { + Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme()); + EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath()); + return false; + } + + switch (requestCode) { + case REQUEST_CODE_CROP_PHOTO: + mActivity.returnUriResult(pictureUri); + return true; + case REQUEST_CODE_TAKE_PHOTO: + case REQUEST_CODE_CHOOSE_PHOTO: + if (mTakePictureUri.equals(pictureUri)) { + if (PhotoCapabilityUtils.canCropPhoto(mActivity)) { + cropPhoto(); + } else { + onPhotoNotCropped(pictureUri); + } + } else { + copyAndCropPhoto(pictureUri); + } + return true; + } + return false; + } + + void takePhoto() { + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE); + appendOutputExtra(intent, mTakePictureUri); + mActivity.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO); + } + + void choosePhoto() { + Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES, null); + intent.setType("image/*"); + mActivity.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO); + } + + private void copyAndCropPhoto(final Uri pictureUri) { + // TODO: Replace AsyncTask + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + final ContentResolver cr = mActivity.getContentResolver(); + try (InputStream in = cr.openInputStream(pictureUri); + OutputStream out = cr.openOutputStream(mTakePictureUri)) { + Streams.copy(in, out); + } catch (IOException e) { + Log.w(TAG, "Failed to copy photo", e); + } + return null; + } + + @Override + protected void onPostExecute(Void result) { + if (!mActivity.isFinishing() && !mActivity.isDestroyed()) { + cropPhoto(); + } + } + }.execute(); + } + + private void cropPhoto() { + // TODO: Use a public intent, when there is one. + Intent intent = new Intent("com.android.camera.action.CROP"); + intent.setDataAndType(mTakePictureUri, "image/*"); + appendOutputExtra(intent, mCropPictureUri); + appendCropExtras(intent); + if (intent.resolveActivity(mActivity.getPackageManager()) != null) { + try { + StrictMode.disableDeathOnFileUriExposure(); + mActivity.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO); + } finally { + StrictMode.enableDeathOnFileUriExposure(); + } + } else { + onPhotoNotCropped(mTakePictureUri); + } + } + + private void appendOutputExtra(Intent intent, Uri pictureUri) { + intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri)); + } + + private void appendCropExtras(Intent intent) { + intent.putExtra("crop", "true"); + intent.putExtra("scale", true); + intent.putExtra("scaleUpIfNeeded", true); + intent.putExtra("aspectX", 1); + intent.putExtra("aspectY", 1); + intent.putExtra("outputX", mPhotoSize); + intent.putExtra("outputY", mPhotoSize); + } + + private void onPhotoNotCropped(final Uri data) { + // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change + new AsyncTask<Void, Void, Bitmap>() { + @Override + protected Bitmap doInBackground(Void... params) { + // Scale and crop to a square aspect ratio + Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize, + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(croppedImage); + Bitmap fullImage; + try { + InputStream imageStream = mActivity.getContentResolver() + .openInputStream(data); + fullImage = BitmapFactory.decodeStream(imageStream); + } catch (FileNotFoundException fe) { + return null; + } + if (fullImage != null) { + int rotation = getRotation(mActivity, data); + final int squareSize = Math.min(fullImage.getWidth(), + fullImage.getHeight()); + final int left = (fullImage.getWidth() - squareSize) / 2; + final int top = (fullImage.getHeight() - squareSize) / 2; + + Matrix matrix = new Matrix(); + RectF rectSource = new RectF(left, top, + left + squareSize, top + squareSize); + RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize); + matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER); + matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f); + canvas.drawBitmap(fullImage, matrix, new Paint()); + return croppedImage; + } else { + // Bah! Got nothin. + return null; + } + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME)); + mActivity.returnUriResult(mCropPictureUri); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + + /** + * Reads the image's exif data and determines the rotation degree needed to display the image + * in portrait mode. + */ + private int getRotation(Context context, Uri selectedImage) { + int rotation = -1; + try { + InputStream imageStream = context.getContentResolver().openInputStream(selectedImage); + ExifInterface exif = new ExifInterface(imageStream); + rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1); + } catch (IOException exception) { + Log.e(TAG, "Error while getting rotation", exception); + } + + switch (rotation) { + case ExifInterface.ORIENTATION_ROTATE_90: + return 90; + case ExifInterface.ORIENTATION_ROTATE_180: + return 180; + case ExifInterface.ORIENTATION_ROTATE_270: + return 270; + default: + return 0; + } + } + + private void saveBitmapToFile(Bitmap bitmap, File file) { + try { + OutputStream os = new FileOutputStream(file); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, os); + os.flush(); + os.close(); + } catch (IOException e) { + Log.e(TAG, "Cannot create temp file", e); + } + } + + private static int getPhotoSize(Context context) { + try (Cursor cursor = context.getContentResolver().query( + ContactsContract.DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, + new String[]{ContactsContract.DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) { + if (cursor != null) { + cursor.moveToFirst(); + return cursor.getInt(0); + } else { + return DEFAULT_PHOTO_SIZE; + } + } + } + + private Uri createTempImageUri(Context context, String fileName, boolean purge) { + final File fullPath = new File(mImagesDir, fileName); + if (purge) { + fullPath.delete(); + } + return FileProvider.getUriForFile(context, mFileAuthority, fullPath); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java new file mode 100644 index 000000000000..50015e653399 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.users; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.core.graphics.drawable.RoundedBitmapDrawable; +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.internal.util.UserIcons; +import com.android.settingslib.R; + +import com.google.android.setupcompat.template.FooterBarMixin; +import com.google.android.setupcompat.template.FooterButton; +import com.google.android.setupdesign.GlifLayout; +import com.google.android.setupdesign.util.ThemeHelper; + +import java.util.ArrayList; +import java.util.List; + +/** + * Activity to allow the user to choose a user profile picture. + * + * <p>Options are provided to take a photo or choose a photo using the photo picker. In addition, + * preselected avatar images may be provided in the resource array {@code avatar_images}. If + * provided, every element of that array must be a bitmap drawable. + * + * <p>If preselected images are not provided, the default avatar will be shown instead, in a range + * of colors. + * + * <p>This activity should be started with startActivityForResult. If a photo or a preselected image + * is selected, a Uri will be returned in the data field of the result intent. If a colored default + * avatar is selected, the chosen color will be returned as {@code EXTRA_DEFAULT_ICON_TINT_COLOR} + * and the data field will be empty. + */ +public class AvatarPickerActivity extends Activity { + + static final String EXTRA_FILE_AUTHORITY = "file_authority"; + static final String EXTRA_DEFAULT_ICON_TINT_COLOR = "default_icon_tint_color"; + + private static final String KEY_AWAITING_RESULT = "awaiting_result"; + private static final String KEY_SELECTED_POSITION = "selected_position"; + + private boolean mWaitingForActivityResult; + + private FooterButton mDoneButton; + private AvatarAdapter mAdapter; + + private AvatarPhotoController mAvatarPhotoController; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ThemeHelper.trySetDynamicColor(this); + setContentView(R.layout.avatar_picker); + setUpButtons(); + + RecyclerView recyclerView = findViewById(R.id.avatar_grid); + mAdapter = new AvatarAdapter(); + recyclerView.setAdapter(mAdapter); + recyclerView.setLayoutManager(new GridLayoutManager(this, + getResources().getInteger(R.integer.avatar_picker_columns))); + + restoreState(savedInstanceState); + + mAvatarPhotoController = new AvatarPhotoController( + this, mWaitingForActivityResult, getFileAuthority()); + } + + private void setUpButtons() { + GlifLayout glifLayout = findViewById(R.id.glif_layout); + FooterBarMixin mixin = glifLayout.getMixin(FooterBarMixin.class); + + FooterButton secondaryButton = + new FooterButton.Builder(this) + .setText("Cancel") + .setListener(view -> cancel()) + .build(); + + mDoneButton = + new FooterButton.Builder(this) + .setText("Done") + .setListener(view -> mAdapter.returnSelectionResult()) + .build(); + mDoneButton.setEnabled(false); + + mixin.setSecondaryButton(secondaryButton); + mixin.setPrimaryButton(mDoneButton); + } + + private String getFileAuthority() { + String authority = getIntent().getStringExtra(EXTRA_FILE_AUTHORITY); + if (authority == null) { + throw new IllegalStateException("File authority must be provided"); + } + return authority; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + mWaitingForActivityResult = false; + mAvatarPhotoController.onActivityResult(requestCode, resultCode, data); + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult); + outState.putInt(KEY_SELECTED_POSITION, mAdapter.mSelectedPosition); + super.onSaveInstanceState(outState); + } + + private void restoreState(Bundle savedInstanceState) { + if (savedInstanceState != null) { + mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false); + mAdapter.mSelectedPosition = + savedInstanceState.getInt(KEY_SELECTED_POSITION, AvatarAdapter.NONE); + } + } + + @Override + public void startActivityForResult(Intent intent, int requestCode) { + mWaitingForActivityResult = true; + super.startActivityForResult(intent, requestCode); + } + + void returnUriResult(Uri uri) { + Intent resultData = new Intent(); + resultData.setData(uri); + setResult(RESULT_OK, resultData); + finish(); + } + + void returnColorResult(int color) { + Intent resultData = new Intent(); + resultData.putExtra(EXTRA_DEFAULT_ICON_TINT_COLOR, color); + setResult(RESULT_OK, resultData); + finish(); + } + + private void cancel() { + setResult(RESULT_CANCELED); + finish(); + } + + private class AvatarAdapter extends RecyclerView.Adapter<AvatarViewHolder> { + + private static final int NONE = -1; + + private final int mTakePhotoPosition; + private final int mChoosePhotoPosition; + private final int mPreselectedImageStartPosition; + + private final List<Drawable> mImageDrawables; + private final TypedArray mPreselectedImages; + private final int[] mUserIconColors; + private int mSelectedPosition = NONE; + + AvatarAdapter() { + final boolean canTakePhoto = + PhotoCapabilityUtils.canTakePhoto(AvatarPickerActivity.this); + final boolean canChoosePhoto = + PhotoCapabilityUtils.canChoosePhoto(AvatarPickerActivity.this); + mTakePhotoPosition = (canTakePhoto ? 0 : NONE); + mChoosePhotoPosition = (canChoosePhoto ? (canTakePhoto ? 1 : 0) : NONE); + mPreselectedImageStartPosition = (canTakePhoto ? 1 : 0) + (canChoosePhoto ? 1 : 0); + + mPreselectedImages = getResources().obtainTypedArray(R.array.avatar_images); + mUserIconColors = UserIcons.getUserIconColors(getResources()); + mImageDrawables = buildDrawableList(); + } + + @NonNull + @Override + public AvatarViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) { + LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + View itemView = layoutInflater.inflate(R.layout.avatar_item, parent, false); + return new AvatarViewHolder(itemView); + } + + @Override + public void onBindViewHolder(@NonNull AvatarViewHolder viewHolder, int position) { + if (position == mTakePhotoPosition) { + viewHolder.setDrawable(getDrawable(R.drawable.avatar_take_photo_circled)); + viewHolder.setClickListener(view -> mAvatarPhotoController.takePhoto()); + + } else if (position == mChoosePhotoPosition) { + viewHolder.setDrawable(getDrawable(R.drawable.avatar_choose_photo_circled)); + viewHolder.setClickListener(view -> mAvatarPhotoController.choosePhoto()); + + } else if (position >= mPreselectedImageStartPosition) { + viewHolder.setSelected(position == mSelectedPosition); + viewHolder.setDrawable(mImageDrawables.get(indexFromPosition(position))); + viewHolder.setClickListener(view -> { + if (mSelectedPosition == position) { + deselect(position); + } else { + select(position); + } + }); + } + } + + @Override + public int getItemCount() { + return mPreselectedImageStartPosition + mImageDrawables.size(); + } + + private List<Drawable> buildDrawableList() { + List<Drawable> result = new ArrayList<>(); + + for (int i = 0; i < mPreselectedImages.length(); i++) { + Drawable drawable = mPreselectedImages.getDrawable(i); + if (drawable instanceof BitmapDrawable) { + result.add(circularDrawableFrom((BitmapDrawable) drawable)); + } else { + throw new IllegalStateException("Avatar drawables must be bitmaps"); + } + } + if (!result.isEmpty()) { + return result; + } + + // No preselected images. Use tinted default icon. + for (int i = 0; i < mUserIconColors.length; i++) { + result.add(UserIcons.getDefaultUserIconInColor(getResources(), mUserIconColors[i])); + } + return result; + } + + private Drawable circularDrawableFrom(BitmapDrawable drawable) { + Bitmap bitmap = drawable.getBitmap(); + + RoundedBitmapDrawable roundedBitmapDrawable = + RoundedBitmapDrawableFactory.create(getResources(), bitmap); + roundedBitmapDrawable.setCircular(true); + + return roundedBitmapDrawable; + } + + private int indexFromPosition(int position) { + return position - mPreselectedImageStartPosition; + } + + private void select(int position) { + final int oldSelection = mSelectedPosition; + mSelectedPosition = position; + notifyItemChanged(position); + if (oldSelection != NONE) { + notifyItemChanged(oldSelection); + } else { + mDoneButton.setEnabled(true); + } + } + + private void deselect(int position) { + mSelectedPosition = NONE; + notifyItemChanged(position); + mDoneButton.setEnabled(false); + } + + private void returnSelectionResult() { + int index = indexFromPosition(mSelectedPosition); + if (mPreselectedImages.length() > 0) { + int resourceId = mPreselectedImages.getResourceId(index, -1); + if (resourceId == -1) { + throw new IllegalStateException("Preselected avatar images must be resources."); + } + returnUriResult(uriForResourceId(resourceId)); + } else { + returnColorResult( + mUserIconColors[index]); + } + } + + private Uri uriForResourceId(int resourceId) { + return new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(getResources().getResourcePackageName(resourceId)) + .appendPath(getResources().getResourceTypeName(resourceId)) + .appendPath(getResources().getResourceEntryName(resourceId)) + .build(); + } + } + + private static class AvatarViewHolder extends RecyclerView.ViewHolder { + private final ImageView mImageView; + + AvatarViewHolder(View view) { + super(view); + mImageView = view.findViewById(R.id.avatar_image); + } + + public void setDrawable(Drawable drawable) { + mImageView.setImageDrawable(drawable); + } + + public void setClickListener(View.OnClickListener listener) { + mImageView.setOnClickListener(listener); + } + + public void setSelected(boolean selected) { + mImageView.setSelected(selected); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java index 58599532d9cb..80ee86f5e489 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java @@ -25,6 +25,7 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; +import android.os.UserManager; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; @@ -36,6 +37,8 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.util.UserIcons; import com.android.settingslib.R; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.drawable.CircleFramedDrawable; import java.io.File; @@ -139,13 +142,20 @@ public class EditUserInfoController { Drawable userIcon = getUserIcon(activity, defaultUserIcon); userPhotoView.setImageDrawable(userIcon); - if (canChangePhoto(activity)) { - mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter, - userPhotoView); + if (isChangePhotoRestrictedByBase(activity)) { + // some users can't change their photos so we need to remove the suggestive icon + content.findViewById(R.id.add_a_photo_icon).setVisibility(View.GONE); } else { - // some users can't change their photos so we need to remove suggestive - // background from the photoView - userPhotoView.setBackground(null); + RestrictedLockUtils.EnforcedAdmin adminRestriction = + getChangePhotoAdminRestriction(activity); + if (adminRestriction != null) { + userPhotoView.setOnClickListener(view -> + RestrictedLockUtils.sendShowAdminSupportDetailsIntent( + activity, adminRestriction)); + } else { + mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter, + userPhotoView); + } } mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon, @@ -204,16 +214,21 @@ public class EditUserInfoController { } @VisibleForTesting - boolean canChangePhoto(Context context) { - return (PhotoCapabilityUtils.canCropPhoto(context) - && PhotoCapabilityUtils.canChoosePhoto(context)) - || PhotoCapabilityUtils.canTakePhoto(context); + boolean isChangePhotoRestrictedByBase(Context context) { + return RestrictedLockUtilsInternal.hasBaseUserRestriction( + context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId()); + } + + @VisibleForTesting + RestrictedLockUtils.EnforcedAdmin getChangePhotoAdminRestriction(Context context) { + return RestrictedLockUtilsInternal.checkIfRestrictionEnforced( + context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId()); } @VisibleForTesting EditUserPhotoController createEditUserPhotoController(Activity activity, ActivityStarter activityStarter, ImageView userPhotoView) { return new EditUserPhotoController(activity, activityStarter, userPhotoView, - mSavedPhoto, mWaitingForActivityResult, mFileAuthority); + mSavedPhoto, mFileAuthority); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java index f9584a3e15e9..f8bb38b5978e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java @@ -16,46 +16,21 @@ package com.android.settingslib.users; +import android.annotation.NonNull; import android.app.Activity; -import android.content.ClipData; -import android.content.ContentResolver; -import android.content.Context; import android.content.Intent; -import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.RectF; import android.graphics.drawable.Drawable; -import android.media.ExifInterface; import android.net.Uri; -import android.os.AsyncTask; -import android.os.StrictMode; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.ContactsContract.DisplayPhoto; -import android.provider.MediaStore; -import android.util.EventLog; import android.util.Log; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; import android.widget.ImageView; -import android.widget.ListPopupWindow; -import android.widget.TextView; - -import androidx.core.content.FileProvider; +import com.android.internal.util.UserIcons; import com.android.settingslib.R; -import com.android.settingslib.RestrictedLockUtils; -import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.drawable.CircleFramedDrawable; - -import libcore.io.Streams; +import com.android.settingslib.utils.ThreadUtils; import java.io.File; import java.io.FileNotFoundException; @@ -63,8 +38,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; +import java.util.concurrent.ExecutionException; /** * This class contains logic for starting activities to take/choose/crop photo, reads and transforms @@ -75,45 +49,30 @@ public class EditUserPhotoController { // It seems that this class generates custom request codes and they may // collide with ours, these values are very unlikely to have a conflict. - private static final int REQUEST_CODE_CHOOSE_PHOTO = 1001; - private static final int REQUEST_CODE_TAKE_PHOTO = 1002; - private static final int REQUEST_CODE_CROP_PHOTO = 1003; - // in rare cases we get a null Cursor when querying for DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI - // so we need a default photo size - private static final int DEFAULT_PHOTO_SIZE = 500; + private static final int REQUEST_CODE_PICK_AVATAR = 1004; private static final String IMAGES_DIR = "multi_user"; - private static final String CROP_PICTURE_FILE_NAME = "CropEditUserPhoto.jpg"; - private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto.jpg"; private static final String NEW_USER_PHOTO_FILE_NAME = "NewUserPhoto.png"; - private final int mPhotoSize; - private final Activity mActivity; private final ActivityStarter mActivityStarter; private final ImageView mImageView; private final String mFileAuthority; private final File mImagesDir; - private final Uri mCropPictureUri; - private final Uri mTakePictureUri; - private Bitmap mNewUserPhotoBitmap; private Drawable mNewUserPhotoDrawable; public EditUserPhotoController(Activity activity, ActivityStarter activityStarter, - ImageView view, Bitmap bitmap, boolean waiting, String fileAuthority) { + ImageView view, Bitmap bitmap, String fileAuthority) { mActivity = activity; mActivityStarter = activityStarter; - mImageView = view; mFileAuthority = fileAuthority; mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR); mImagesDir.mkdir(); - mCropPictureUri = createTempImageUri(activity, CROP_PICTURE_FILE_NAME, !waiting); - mTakePictureUri = createTempImageUri(activity, TAKE_PICTURE_FILE_NAME, !waiting); - mPhotoSize = getPhotoSize(activity); - mImageView.setOnClickListener(v -> showUpdatePhotoPopup()); + mImageView = view; + mImageView.setOnClickListener(v -> showAvatarPicker()); mNewUserPhotoBitmap = bitmap; } @@ -125,32 +84,19 @@ public class EditUserPhotoController { if (resultCode != Activity.RESULT_OK) { return false; } - final Uri pictureUri = data != null && data.getData() != null - ? data.getData() : mTakePictureUri; - // Check if the result is a content uri - if (!ContentResolver.SCHEME_CONTENT.equals(pictureUri.getScheme())) { - Log.e(TAG, "Invalid pictureUri scheme: " + pictureUri.getScheme()); - EventLog.writeEvent(0x534e4554, "172939189", -1, pictureUri.getPath()); - return false; - } - - switch (requestCode) { - case REQUEST_CODE_CROP_PHOTO: - onPhotoCropped(pictureUri); + if (requestCode == REQUEST_CODE_PICK_AVATAR) { + if (data.hasExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR)) { + int tintColor = + data.getIntExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR, -1); + onDefaultIconSelected(tintColor); return true; - case REQUEST_CODE_TAKE_PHOTO: - case REQUEST_CODE_CHOOSE_PHOTO: - if (mTakePictureUri.equals(pictureUri)) { - if (PhotoCapabilityUtils.canCropPhoto(mActivity)) { - cropPhoto(); - } else { - onPhotoNotCropped(pictureUri); - } - } else { - copyAndCropPhoto(pictureUri); - } + } + if (data.getData() != null) { + onPhotoCropped(data.getData()); return true; + } + } return false; } @@ -159,224 +105,60 @@ public class EditUserPhotoController { return mNewUserPhotoDrawable; } - private void showUpdatePhotoPopup() { - final Context context = mImageView.getContext(); - final boolean canTakePhoto = PhotoCapabilityUtils.canTakePhoto(context); - final boolean canChoosePhoto = PhotoCapabilityUtils.canChoosePhoto(context); - - if (!canTakePhoto && !canChoosePhoto) { - return; - } - - final List<EditUserPhotoController.RestrictedMenuItem> items = new ArrayList<>(); - - if (canTakePhoto) { - final String title = context.getString(R.string.user_image_take_photo); - items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON, - this::takePhoto)); - } - - if (canChoosePhoto) { - final String title = context.getString(R.string.user_image_choose_photo); - items.add(new RestrictedMenuItem(context, title, UserManager.DISALLOW_SET_USER_ICON, - this::choosePhoto)); - } - - final ListPopupWindow listPopupWindow = new ListPopupWindow(context); - - listPopupWindow.setAnchorView(mImageView); - listPopupWindow.setModal(true); - listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED); - listPopupWindow.setAdapter(new RestrictedPopupMenuAdapter(context, items)); - - final int width = Math.max(mImageView.getWidth(), context.getResources() - .getDimensionPixelSize(R.dimen.update_user_photo_popup_min_width)); - listPopupWindow.setWidth(width); - listPopupWindow.setDropDownGravity(Gravity.START); - - listPopupWindow.setOnItemClickListener((parent, view, position, id) -> { - listPopupWindow.dismiss(); - final RestrictedMenuItem item = - (RestrictedMenuItem) parent.getAdapter().getItem(position); - item.doAction(); - }); - - listPopupWindow.show(); - } - - private void takePhoto() { - Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE_SECURE); - appendOutputExtra(intent, mTakePictureUri); - mActivityStarter.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO); - } - - private void choosePhoto() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); - intent.setType("image/*"); - appendOutputExtra(intent, mTakePictureUri); - mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CHOOSE_PHOTO); + private void showAvatarPicker() { + Intent intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class); + intent.putExtra(AvatarPickerActivity.EXTRA_FILE_AUTHORITY, mFileAuthority); + mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR); } - private void copyAndCropPhoto(final Uri pictureUri) { - // TODO: Replace AsyncTask - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... params) { - final ContentResolver cr = mActivity.getContentResolver(); - try (InputStream in = cr.openInputStream(pictureUri); - OutputStream out = cr.openOutputStream(mTakePictureUri)) { - Streams.copy(in, out); - } catch (IOException e) { - Log.w(TAG, "Failed to copy photo", e); - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - if (!mActivity.isFinishing() && !mActivity.isDestroyed()) { - cropPhoto(); - } - } - }.execute(); - } + private void onDefaultIconSelected(int tintColor) { + try { + ThreadUtils.postOnBackgroundThread(() -> { + Drawable drawable = + UserIcons.getDefaultUserIconInColor(mActivity.getResources(), tintColor); + Bitmap bitmap = convertToBitmap(drawable, + (int) mActivity.getResources().getDimension(R.dimen.circle_avatar_size)); - private void cropPhoto() { - // TODO: Use a public intent, when there is one. - Intent intent = new Intent("com.android.camera.action.CROP"); - intent.setDataAndType(mTakePictureUri, "image/*"); - appendOutputExtra(intent, mCropPictureUri); - appendCropExtras(intent); - if (intent.resolveActivity(mActivity.getPackageManager()) != null) { - try { - StrictMode.disableDeathOnFileUriExposure(); - mActivityStarter.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO); - } finally { - StrictMode.enableDeathOnFileUriExposure(); - } - } else { - onPhotoNotCropped(mTakePictureUri); + ThreadUtils.postOnMainThread(() -> onPhotoProcessed(bitmap)); + }).get(); + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "Error processing default icon", e); } } - private void appendOutputExtra(Intent intent, Uri pictureUri) { - intent.putExtra(MediaStore.EXTRA_OUTPUT, pictureUri); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION - | Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, pictureUri)); - } - - private void appendCropExtras(Intent intent) { - intent.putExtra("crop", "true"); - intent.putExtra("scale", true); - intent.putExtra("scaleUpIfNeeded", true); - intent.putExtra("aspectX", 1); - intent.putExtra("aspectY", 1); - intent.putExtra("outputX", mPhotoSize); - intent.putExtra("outputY", mPhotoSize); + private static Bitmap convertToBitmap(@NonNull Drawable icon, int size) { + Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + icon.setBounds(0, 0, size, size); + icon.draw(canvas); + return bitmap; } private void onPhotoCropped(final Uri data) { - // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change - new AsyncTask<Void, Void, Bitmap>() { - @Override - protected Bitmap doInBackground(Void... params) { - InputStream imageStream = null; - try { - imageStream = mActivity.getContentResolver() - .openInputStream(data); - return BitmapFactory.decodeStream(imageStream); - } catch (FileNotFoundException fe) { - Log.w(TAG, "Cannot find image file", fe); - return null; - } finally { - if (imageStream != null) { - try { - imageStream.close(); - } catch (IOException ioe) { - Log.w(TAG, "Cannot close image stream", ioe); - } + ThreadUtils.postOnBackgroundThread(() -> { + InputStream imageStream = null; + Bitmap bitmap = null; + try { + imageStream = mActivity.getContentResolver() + .openInputStream(data); + bitmap = BitmapFactory.decodeStream(imageStream); + } catch (FileNotFoundException fe) { + Log.w(TAG, "Cannot find image file", fe); + } finally { + if (imageStream != null) { + try { + imageStream.close(); + } catch (IOException ioe) { + Log.w(TAG, "Cannot close image stream", ioe); } } } - @Override - protected void onPostExecute(Bitmap bitmap) { - onPhotoProcessed(bitmap); - - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); - } - - private void onPhotoNotCropped(final Uri data) { - // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change - new AsyncTask<Void, Void, Bitmap>() { - @Override - protected Bitmap doInBackground(Void... params) { - // Scale and crop to a square aspect ratio - Bitmap croppedImage = Bitmap.createBitmap(mPhotoSize, mPhotoSize, - Config.ARGB_8888); - Canvas canvas = new Canvas(croppedImage); - Bitmap fullImage; - try { - InputStream imageStream = mActivity.getContentResolver() - .openInputStream(data); - fullImage = BitmapFactory.decodeStream(imageStream); - } catch (FileNotFoundException fe) { - return null; - } - if (fullImage != null) { - int rotation = getRotation(mActivity, data); - final int squareSize = Math.min(fullImage.getWidth(), - fullImage.getHeight()); - final int left = (fullImage.getWidth() - squareSize) / 2; - final int top = (fullImage.getHeight() - squareSize) / 2; - - Matrix matrix = new Matrix(); - RectF rectSource = new RectF(left, top, - left + squareSize, top + squareSize); - RectF rectDest = new RectF(0, 0, mPhotoSize, mPhotoSize); - matrix.setRectToRect(rectSource, rectDest, Matrix.ScaleToFit.CENTER); - matrix.postRotate(rotation, mPhotoSize / 2f, mPhotoSize / 2f); - canvas.drawBitmap(fullImage, matrix, new Paint()); - return croppedImage; - } else { - // Bah! Got nothin. - return null; - } - } - - @Override - protected void onPostExecute(Bitmap bitmap) { - onPhotoProcessed(bitmap); + if (bitmap != null) { + Bitmap finalBitmap = bitmap; + ThreadUtils.postOnMainThread(() -> onPhotoProcessed(finalBitmap)); } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); - } - - /** - * Reads the image's exif data and determines the rotation degree needed to display the image - * in portrait mode. - */ - private int getRotation(Context context, Uri selectedImage) { - int rotation = -1; - try { - InputStream imageStream = context.getContentResolver().openInputStream(selectedImage); - ExifInterface exif = new ExifInterface(imageStream); - rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1); - } catch (IOException exception) { - Log.e(TAG, "Error while getting rotation", exception); - } - - switch (rotation) { - case ExifInterface.ORIENTATION_ROTATE_90: - return 90; - case ExifInterface.ORIENTATION_ROTATE_180: - return 180; - case ExifInterface.ORIENTATION_ROTATE_270: - return 270; - default: - return 0; - } + }); } private void onPhotoProcessed(Bitmap bitmap) { @@ -386,29 +168,6 @@ public class EditUserPhotoController { .getInstance(mImageView.getContext(), mNewUserPhotoBitmap); mImageView.setImageDrawable(mNewUserPhotoDrawable); } - new File(mImagesDir, TAKE_PICTURE_FILE_NAME).delete(); - new File(mImagesDir, CROP_PICTURE_FILE_NAME).delete(); - } - - private static int getPhotoSize(Context context) { - try (Cursor cursor = context.getContentResolver().query( - DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, - new String[]{DisplayPhoto.DISPLAY_MAX_DIM}, null, null, null)) { - if (cursor != null) { - cursor.moveToFirst(); - return cursor.getInt(0); - } else { - return DEFAULT_PHOTO_SIZE; - } - } - } - - private Uri createTempImageUri(Context context, String fileName, boolean purge) { - final File fullPath = new File(mImagesDir, fileName); - if (purge) { - fullPath.delete(); - } - return FileProvider.getUriForFile(context, mFileAuthority, fullPath); } File saveNewUserPhotoBitmap() { @@ -435,84 +194,4 @@ public class EditUserPhotoController { void removeNewUserPhotoBitmapFile() { new File(mImagesDir, NEW_USER_PHOTO_FILE_NAME).delete(); } - - private static final class RestrictedMenuItem { - private final Context mContext; - private final String mTitle; - private final Runnable mAction; - private final RestrictedLockUtils.EnforcedAdmin mAdmin; - // Restriction may be set by system or something else via UserManager.setUserRestriction(). - private final boolean mIsRestrictedByBase; - - /** - * The menu item, used for popup menu. Any element of such a menu can be disabled by admin. - * - * @param context A context. - * @param title The title of the menu item. - * @param restriction The restriction, that if is set, blocks the menu item. - * @param action The action on menu item click. - */ - RestrictedMenuItem(Context context, String title, String restriction, - Runnable action) { - mContext = context; - mTitle = title; - mAction = action; - - final int myUserId = UserHandle.myUserId(); - mAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context, - restriction, myUserId); - mIsRestrictedByBase = RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, - restriction, myUserId); - } - - @Override - public String toString() { - return mTitle; - } - - void doAction() { - if (isRestrictedByBase()) { - return; - } - - if (isRestrictedByAdmin()) { - RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mAdmin); - return; - } - - mAction.run(); - } - - boolean isRestrictedByAdmin() { - return mAdmin != null; - } - - boolean isRestrictedByBase() { - return mIsRestrictedByBase; - } - } - - /** - * Provide this adapter to ListPopupWindow.setAdapter() to have a popup window menu, where - * any element can be restricted by admin (profile owner or device owner). - */ - private static final class RestrictedPopupMenuAdapter extends ArrayAdapter<RestrictedMenuItem> { - RestrictedPopupMenuAdapter(Context context, List<RestrictedMenuItem> items) { - super(context, R.layout.restricted_popup_menu_item, R.id.text, items); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final View view = super.getView(position, convertView, parent); - final RestrictedMenuItem item = getItem(position); - final TextView text = (TextView) view.findViewById(R.id.text); - final ImageView image = (ImageView) view.findViewById(R.id.restricted_icon); - - text.setEnabled(!item.isRestrictedByAdmin() && !item.isRestrictedByBase()); - image.setVisibility(item.isRestrictedByAdmin() && !item.isRestrictedByBase() - ? ImageView.VISIBLE : ImageView.GONE); - - return view; - } - } } diff --git a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java index 165c2808f16d..b8615a7e5aa9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/PhotoCapabilityUtils.java @@ -40,12 +40,12 @@ public class PhotoCapabilityUtils { /** * Check if the current user can perform any activity for - * android.intent.action.GET_CONTENT action for images. + * ACTION_PICK_IMAGES action for images. * Returns false if the device is currently locked and * requires a PIN, pattern or password to unlock. */ public static boolean canChoosePhoto(Context context) { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES); intent.setType("image/*"); boolean canPerformActivityForGetImage = context.getPackageManager().queryIntentActivities(intent, 0).size() > 0; 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/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java index c1cc3ae9778a..445701fa7244 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java @@ -18,6 +18,7 @@ package com.android.settingslib.deviceinfo; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; @@ -33,7 +34,6 @@ import android.os.Handler; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -55,7 +55,6 @@ public class ConnectivityPreferenceControllerTest { } @Test - @Ignore public void testBroadcastReceiver() { final AbstractConnectivityPreferenceController preferenceController = spy(new ConcreteConnectivityPreferenceController(mContext, mLifecycle)); @@ -73,7 +72,7 @@ public class ConnectivityPreferenceControllerTest { verify(mContext, times(1)) .registerReceiver(receiverArgumentCaptor.capture(), filterArgumentCaptor.capture(), - anyString(), nullable(Handler.class)); + anyString(), nullable(Handler.class), anyInt()); final BroadcastReceiver receiver = receiverArgumentCaptor.getValue(); final IntentFilter filter = filterArgumentCaptor.getValue(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java index d6c8816ecc58..a5ee4c35f724 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java @@ -62,7 +62,7 @@ public class EditUserInfoControllerTest { @Mock private ActivityStarter mActivityStarter; - private boolean mCanChangePhoto; + private boolean mPhotoRestrictedByBase; private Activity mActivity; private TestEditUserInfoController mController; @@ -85,8 +85,8 @@ public class EditUserInfoControllerTest { } @Override - boolean canChangePhoto(Context context) { - return mCanChangePhoto; + boolean isChangePhotoRestrictedByBase(Context context) { + return mPhotoRestrictedByBase; } } @@ -96,7 +96,7 @@ public class EditUserInfoControllerTest { mActivity = spy(ActivityController.of(new FragmentActivity()).get()); mActivity.setTheme(R.style.Theme_AppCompat_DayNight); mController = new TestEditUserInfoController(); - mCanChangePhoto = true; + mPhotoRestrictedByBase = true; } @Test @@ -260,7 +260,7 @@ public class EditUserInfoControllerTest { @Test public void createDialog_canNotChangePhoto_nullPhotoController() { - mCanChangePhoto = false; + mPhotoRestrictedByBase = false; mController.createDialog(mActivity, mActivityStarter, mCurrentIcon, "test", "title", null, null); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java index 10ac829186d4..3b18c57e28fa 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java @@ -64,6 +64,20 @@ public class FooterPreferenceTest { } @Test + public void setLearnMoreText_shouldSetAsTextInLearnMore() { + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)); + mFooterPreference.setLearnMoreText("Custom learn more"); + mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */); + + mFooterPreference.onBindViewHolder(holder); + + assertThat(((TextView) holder.findViewById( + R.id.settingslib_learn_more)).getText().toString()) + .isEqualTo("Custom learn more"); + } + + @Test public void setContentDescription_contentSet_shouldGetSameContentDescription() { mFooterPreference.setContentDescription("test"); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 52a708d85bba..13ae87015fa4 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -326,6 +326,8 @@ public class SettingsBackupTest { Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX, Settings.Global.LOW_POWER_MODE_STICKY, Settings.Global.LOW_POWER_MODE_SUGGESTION_PARAMS, + Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE, + Settings.Global.LOW_POWER_STANDBY_ENABLED, Settings.Global.LTE_SERVICE_FORCED, Settings.Global.LID_BEHAVIOR, Settings.Global.MAX_ERROR_BYTES_PREFIX, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index ebff00c59732..27fc6ba47ec9 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -248,6 +248,7 @@ <uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" /> <uses-permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" /> <uses-permission android:name="android.permission.MANAGE_APP_PREDICTIONS" /> + <uses-permission android:name="android.permission.MANAGE_LOW_POWER_STANDBY" /> <uses-permission android:name="android.permission.MANAGE_SEARCH_UI" /> <uses-permission android:name="android.permission.MANAGE_SMARTSPACE" /> <uses-permission android:name="android.permission.MANAGE_WALLPAPER_EFFECTS_GENERATION" /> diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index 68c8c3e02a42..ffee8946e016 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -20,6 +20,7 @@ import android.app.PendingIntent; import android.app.smartspace.SmartspaceAction; import android.app.smartspace.SmartspaceTarget; import android.app.smartspace.SmartspaceTargetEvent; +import android.app.smartspace.uitemplatedata.SmartspaceTapAction; import android.content.ActivityNotFoundException; import android.content.Intent; import android.graphics.drawable.Drawable; @@ -143,6 +144,18 @@ public interface BcSmartspaceDataPlugin extends Plugin { } } + default void startFromAction(SmartspaceTapAction action, View v, boolean showOnLockscreen) { + try { + if (action.getIntent() != null) { + startIntent(v, action.getIntent(), showOnLockscreen); + } else if (action.getPendingIntent() != null) { + startPendingIntent(action.getPendingIntent(), showOnLockscreen); + } + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Could not launch intent for action: " + action, e); + } + } + /** Start the intent */ void startIntent(View v, Intent i, boolean showOnLockscreen); 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_complications_layer.xml b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml index 51359471ff98..2d565a1a04fc 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml +++ b/packages/SystemUI/res/layout/dream_overlay_complications_layer.xml @@ -31,6 +31,7 @@ android:shadowRadius="2.0" android:singleLine="true" android:textSize="72sp" + android:textColor="@android:color/white" app:layout_constraintBottom_toTopOf="@+id/date_view" app:layout_constraintStart_toStartOf="parent" /> <TextClock @@ -43,7 +44,32 @@ android:format24Hour="EEE, MMM d" android:singleLine="true" android:textSize="18sp" + android:textColor="@android:color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="@+id/time_view" app:layout_constraintStart_toStartOf="@+id/time_view" /> + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + 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: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/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-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-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/dimens.xml b/packages/SystemUI/res/values/dimens.xml index f94031c73e3c..800dd0a6d7a3 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -369,6 +369,13 @@ <!-- The top margin of the panel that holds the list of notifications. --> <dimen name="notification_panel_margin_top">0dp</dimen> + <!-- The minimum content height for the split shade NSSL. + It is used because if the height is too small, the expansion motion is too fast. + Note that the value of 256dp is more or less a random value and can be changed to tweak + the expansion motion. + --> + <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> @@ -1028,6 +1035,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> @@ -1336,4 +1344,37 @@ <!-- 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> + + <!-- 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/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/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 5d092d02a835..eebc7918c72c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -120,6 +120,8 @@ public class QuickStepContract { public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22; // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it. public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23; + // The current app is in immersive mode + public static final int SYSUI_STATE_IMMERSIVE_MODE = 1 << 24; @Retention(RetentionPolicy.SOURCE) @IntDef({SYSUI_STATE_SCREEN_PINNING, @@ -145,7 +147,8 @@ public class QuickStepContract { SYSUI_STATE_IME_SWITCHER_SHOWING, SYSUI_STATE_DEVICE_DOZING, SYSUI_STATE_BACK_DISABLED, - SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED + SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, + SYSUI_STATE_IMMERSIVE_MODE }) public @interface SystemUiStateFlags {} @@ -179,6 +182,7 @@ public class QuickStepContract { str.add((flags & SYSUI_STATE_BACK_DISABLED) != 0 ? "back_disabled" : ""); str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0 ? "bubbles_mange_menu_expanded" : ""); + str.add((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0 ? "immersive_mode" : ""); return str.toString(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt index 214b284ac4b9..43cd764f0110 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt @@ -16,6 +16,7 @@ package com.android.keyguard +import android.app.StatusBarManager.SESSION_KEYGUARD import android.content.Context import android.hardware.biometrics.BiometricSourceType import com.android.internal.annotations.VisibleForTesting @@ -28,7 +29,7 @@ import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUT import com.android.keyguard.KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dump.DumpManager +import com.android.systemui.log.SessionTracker import java.io.FileDescriptor import java.io.PrintWriter import javax.inject.Inject @@ -44,7 +45,7 @@ class KeyguardBiometricLockoutLogger @Inject constructor( context: Context?, private val uiEventLogger: UiEventLogger, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val dumpManager: DumpManager + private val sessionTracker: SessionTracker ) : CoreStartable(context) { private var fingerprintLockedOut = false private var faceLockedOut = false @@ -53,7 +54,6 @@ class KeyguardBiometricLockoutLogger @Inject constructor( private var timeout = false override fun start() { - dumpManager.registerDumpable(this) mKeyguardUpdateMonitorCallback.onStrongAuthStateChanged( KeyguardUpdateMonitor.getCurrentUser()) keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback) @@ -65,22 +65,17 @@ class KeyguardBiometricLockoutLogger @Inject constructor( if (biometricSourceType == BiometricSourceType.FINGERPRINT) { val lockedOut = keyguardUpdateMonitor.isFingerprintLockedOut if (lockedOut && !fingerprintLockedOut) { - uiEventLogger.log( - PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT) + log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT) } else if (!lockedOut && fingerprintLockedOut) { - uiEventLogger.log( - PrimaryAuthRequiredEvent - .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET) + log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET) } fingerprintLockedOut = lockedOut } else if (biometricSourceType == BiometricSourceType.FACE) { val lockedOut = keyguardUpdateMonitor.isFaceLockedOut if (lockedOut && !faceLockedOut) { - uiEventLogger.log( - PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT) + log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT) } else if (!lockedOut && faceLockedOut) { - uiEventLogger.log( - PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET) + log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET) } faceLockedOut = lockedOut } @@ -95,20 +90,19 @@ class KeyguardBiometricLockoutLogger @Inject constructor( val newEncryptedOrLockdown = keyguardUpdateMonitor.isEncryptedOrLockdown(userId) if (newEncryptedOrLockdown && !encryptedOrLockdown) { - uiEventLogger.log( - PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN) + log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN) } encryptedOrLockdown = newEncryptedOrLockdown val newUnattendedUpdate = isUnattendedUpdate(strongAuthFlags) if (newUnattendedUpdate && !unattendedUpdate) { - uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE) + log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE) } unattendedUpdate = newUnattendedUpdate val newTimeout = isStrongAuthTimeout(strongAuthFlags) if (newTimeout && !timeout) { - uiEventLogger.log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT) + log(PrimaryAuthRequiredEvent.PRIMARY_AUTH_REQUIRED_TIMEOUT) } timeout = newTimeout } @@ -123,6 +117,9 @@ class KeyguardBiometricLockoutLogger @Inject constructor( ) = containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) || containsFlag(flags, STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT) + private fun log(event: PrimaryAuthRequiredEvent) = + uiEventLogger.log(event, sessionTracker.getSessionId(SESSION_KEYGUARD)) + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) { pw.println(" mFingerprintLockedOut=$fingerprintLockedOut") pw.println(" mFaceLockedOut=$faceLockedOut") diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 49a802235619..57997d8efd6f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static android.app.StatusBarManager.SESSION_KEYGUARD; + import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY; @@ -36,7 +38,10 @@ import android.util.Log; import android.util.Slog; import android.view.MotionEvent; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.InstanceId; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; @@ -53,6 +58,7 @@ import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -86,6 +92,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final UserSwitcherController mUserSwitcherController; private final GlobalSettings mGlobalSettings; private final FeatureFlags mFeatureFlags; + private final SessionTracker mSessionTracker; private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; @@ -191,7 +198,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER) .setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE)); mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS - : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE); + : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE, getSessionId()); } public void reset() { @@ -242,7 +249,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard FalsingManager falsingManager, UserSwitcherController userSwitcherController, FeatureFlags featureFlags, - GlobalSettings globalSettings) { + GlobalSettings globalSettings, + SessionTracker sessionTracker) { super(view); mLockPatternUtils = lockPatternUtils; mUpdateMonitor = keyguardUpdateMonitor; @@ -261,6 +269,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mUserSwitcherController = userSwitcherController; mFeatureFlags = featureFlags; mGlobalSettings = globalSettings; + mSessionTracker = sessionTracker; } @Override @@ -456,7 +465,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard .setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype)); } if (uiEvent != BouncerUiEvent.UNKNOWN) { - mUiEventLogger.log(uiEvent); + mUiEventLogger.log(uiEvent, getSessionId()); } if (finish) { mSecurityCallback.finish(strongAuth, targetUserId); @@ -599,6 +608,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } } + private @Nullable InstanceId getSessionId() { + return mSessionTracker.getSessionId(SESSION_KEYGUARD); + } + /** Update keyguard position based on a tapped X coordinate. */ public void updateKeyguardPosition(float x) { mView.updatePositionByTouchX(x); @@ -622,6 +635,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final GlobalSettings mGlobalSettings; private final FeatureFlags mFeatureFlags; private final UserSwitcherController mUserSwitcherController; + private final SessionTracker mSessionTracker; @Inject Factory(KeyguardSecurityContainer view, @@ -639,7 +653,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard FalsingManager falsingManager, UserSwitcherController userSwitcherController, FeatureFlags featureFlags, - GlobalSettings globalSettings) { + GlobalSettings globalSettings, + SessionTracker sessionTracker) { mView = view; mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory; mLockPatternUtils = lockPatternUtils; @@ -655,6 +670,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mFeatureFlags = featureFlags; mGlobalSettings = globalSettings; mUserSwitcherController = userSwitcherController; + mSessionTracker = sessionTracker; } public KeyguardSecurityContainerController create( @@ -664,7 +680,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, mKeyguardStateController, securityCallback, mSecurityViewFlipperController, mConfigurationController, mFalsingCollector, mFalsingManager, - mUserSwitcherController, mFeatureFlags, mGlobalSettings); + mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker); } } } 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/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/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/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index 96e2302f937c..41287b60fbf8 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -26,10 +26,12 @@ 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.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 +220,18 @@ 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); } 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/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/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/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/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 441e79a97521..ec15b2469358 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -31,6 +31,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_B import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IMMERSIVE_MODE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; @@ -304,6 +305,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, allowSystemGestureIgnoringBarVisibility()) .setFlag(SYSUI_STATE_SCREEN_PINNING, ActivityManagerWrapper.getInstance().isScreenPinningActive()) + .setFlag(SYSUI_STATE_IMMERSIVE_MODE, isImmersiveMode()) .commitUpdate(mDisplayId); } @@ -445,6 +447,10 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; } + private boolean isImmersiveMode() { + return mBehavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + } + @Override public void onConfigurationChanged(Configuration configuration) { mEdgeBackGestureHandler.onConfigurationChanged(configuration); 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..90ed3c6fa5da 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -318,7 +318,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/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/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 0f21a6c0587c..74c97fdbddca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -674,6 +674,12 @@ public class ShadeListBuilder implements Dumpable { // having its summary promoted, regardless of how many children it has Set<String> groupsWithChildrenLostToStability = getGroupsWithChildrenLostToStability(shadeList); + // Like groups which lost a child to stability, any group which lost a child to promotion + // is exempt from having its summary promoted when it has no attached children. + Set<String> groupsWithChildrenLostToPromotionOrStability = + getGroupsWithChildrenLostToPromotion(shadeList); + groupsWithChildrenLostToPromotionOrStability.addAll(groupsWithChildrenLostToStability); + for (int i = 0; i < shadeList.size(); i++) { final ListEntry tle = shadeList.get(i); @@ -683,9 +689,9 @@ public class ShadeListBuilder implements Dumpable { final boolean hasSummary = group.getSummary() != null; if (hasSummary && children.size() == 0) { - if (groupsWithChildrenLostToStability.contains(group.getKey())) { - // This group lost a child on this run to stability, so it is exempt from - // having its summary promoted to the top level, so prune it. + if (groupsWithChildrenLostToPromotionOrStability.contains(group.getKey())) { + // This group lost a child on this run to promotion or stability, so it is + // exempt from having its summary promoted to the top level, so prune it. // It has no children, so it will just vanish. pruneGroupAtIndexAndPromoteAnyChildren(shadeList, group, i); } else { @@ -826,6 +832,26 @@ public class ShadeListBuilder implements Dumpable { } /** + * Collect the keys of any groups which have already lost a child to a {@link NotifPromoter} + * this run. + * + * These groups will be exempt from appearing without any children. + */ + @NonNull + private Set<String> getGroupsWithChildrenLostToPromotion(List<ListEntry> shadeList) { + ArraySet<String> groupsWithChildrenLostToPromotion = new ArraySet<>(); + for (int i = 0; i < shadeList.size(); i++) { + final ListEntry tle = shadeList.get(i); + if (tle.getAttachState().getPromoter() != null) { + // This top-level-entry was part of a group, but was promoted out of it. + final String groupKey = tle.getRepresentativeEntry().getSbn().getGroupKey(); + groupsWithChildrenLostToPromotion.add(groupKey); + } + } + return groupsWithChildrenLostToPromotion; + } + + /** * If a ListEntry was added to the shade list and then later removed (e.g. because it was a * group that was broken up), this method will erase any bookkeeping traces of that addition * and/or check that they were already erased. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index 0bf21af7026b..031132424115 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -15,9 +15,13 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator +import android.app.Notification +import android.app.Notification.GROUP_ALERT_SUMMARY +import android.util.ArrayMap import android.util.ArraySet import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -29,13 +33,13 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.dagger.IncomingHeader -import com.android.systemui.statusbar.notification.interruption.HeadsUpController import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.time.SystemClock import javax.inject.Inject /** @@ -54,27 +58,283 @@ import javax.inject.Inject */ @CoordinatorScope class HeadsUpCoordinator @Inject constructor( + private val mLogger: HeadsUpCoordinatorLogger, + private val mSystemClock: SystemClock, private val mHeadsUpManager: HeadsUpManager, private val mHeadsUpViewBinder: HeadsUpViewBinder, private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider, private val mRemoteInputManager: NotificationRemoteInputManager, @IncomingHeader private val mIncomingHeaderController: NodeController, - @Main private val mExecutor: DelayableExecutor + @Main private val mExecutor: DelayableExecutor, ) : Coordinator { + private val mEntriesBindingUntil = ArrayMap<String, Long>() private var mEndLifetimeExtension: OnEndLifetimeExtensionCallback? = null + private lateinit var mNotifPipeline: NotifPipeline + private var mNow: Long = -1 // notifs we've extended the lifetime for private val mNotifsExtendingLifetime = ArraySet<NotificationEntry>() override fun attach(pipeline: NotifPipeline) { + mNotifPipeline = pipeline mHeadsUpManager.addListener(mOnHeadsUpChangedListener) pipeline.addCollectionListener(mNotifCollectionListener) + pipeline.addOnBeforeTransformGroupsListener(::onBeforeTransformGroups) + pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter) pipeline.addPromoter(mNotifPromoter) pipeline.addNotificationLifetimeExtender(mLifetimeExtender) } private fun onHeadsUpViewBound(entry: NotificationEntry) { mHeadsUpManager.showNotification(entry) + mEntriesBindingUntil.remove(entry.key) + } + + /** + * Once the pipeline starts running, we can look through posted entries and quickly process + * any that don't have groups, and thus will never gave a group alert edge case. + */ + fun onBeforeTransformGroups(list: List<ListEntry>) { + mNow = mSystemClock.currentTimeMillis() + if (mPostedEntries.isEmpty()) { + return + } + // Process all non-group adds/updates + mPostedEntries.values.toList().forEach { posted -> + if (!posted.entry.sbn.isGroup) { + handlePostedEntry(posted, "non-group") + mPostedEntries.remove(posted.key) + } + } + } + + /** + * Once we have a nearly final shade list (not including what's pruned for inflation reasons), + * we know that stability and [NotifPromoter]s have been applied, so we can use the location of + * notifications in this list to determine what kind of group alert behavior should happen. + */ + fun onBeforeFinalizeFilter(list: List<ListEntry>) { + // Nothing to do if there are no other adds/updates + if (mPostedEntries.isEmpty()) { + return + } + // Calculate a bunch of information about the logical group and the locations of group + // entries in the nearly-finalized shade list. These may be used in the per-group loop. + val postedEntriesByGroup = mPostedEntries.values.groupBy { it.entry.sbn.groupKey } + val logicalMembersByGroup = mNotifPipeline.allNotifs.asSequence() + .filter { postedEntriesByGroup.contains(it.sbn.groupKey) } + .groupBy { it.sbn.groupKey } + val groupLocationsByKey: Map<String, GroupLocation> by lazy { getGroupLocationsByKey(list) } + mLogger.logEvaluatingGroups(postedEntriesByGroup.size) + // For each group, determine which notification(s) for a group should alert. + postedEntriesByGroup.forEach { (groupKey, postedEntries) -> + // get and classify the logical members + val logicalMembers = logicalMembersByGroup[groupKey] ?: emptyList() + val logicalSummary = logicalMembers.find { it.sbn.notification.isGroupSummary } + + // Report the start of this group's evaluation + mLogger.logEvaluatingGroup(groupKey, postedEntries.size, logicalMembers.size) + + // If there is no logical summary, then there is no alert to transfer + if (logicalSummary == null) { + postedEntries.forEach { handlePostedEntry(it, "logical-summary-missing") } + return@forEach + } + + // If summary isn't wanted to be heads up, then there is no alert to transfer + if (!isGoingToShowHunStrict(logicalSummary)) { + postedEntries.forEach { handlePostedEntry(it, "logical-summary-not-alerting") } + return@forEach + } + + // The group is alerting! Overall goals: + // - Maybe transfer its alert to a child + // - Also let any/all newly alerting children still alert + var childToReceiveParentAlert: NotificationEntry? + var targetType = "undefined" + + // If the parent is alerting, always look at the posted notification with the newest + // 'when', and if it is isolated with GROUP_ALERT_SUMMARY, then it should receive the + // parent's alert. + childToReceiveParentAlert = + findAlertOverride(postedEntries, groupLocationsByKey::getLocation) + if (childToReceiveParentAlert != null) { + targetType = "alertOverride" + } + + // If the summary is Detached and we have not picked a receiver of the alert, then we + // need to look for the best child to alert in place of the summary. + val isSummaryAttached = groupLocationsByKey.contains(logicalSummary.key) + if (!isSummaryAttached && childToReceiveParentAlert == null) { + childToReceiveParentAlert = + findBestTransferChild(logicalMembers, groupLocationsByKey::getLocation) + if (childToReceiveParentAlert != null) { + targetType = "bestChild" + } + } + + // If there is no child to receive the parent alert, then just handle the posted entries + // and return. + if (childToReceiveParentAlert == null) { + postedEntries.forEach { handlePostedEntry(it, "no-transfer-target") } + return@forEach + } + + // At this point we just need to initiate the transfer + val summaryUpdate = mPostedEntries[logicalSummary.key] + + // If the summary was not attached, then remove the alert from the detached summary. + // Otherwise we can simply ignore its posted update. + if (!isSummaryAttached) { + val summaryUpdateForRemoval = summaryUpdate?.also { + it.shouldHeadsUpEver = false + } ?: PostedEntry(logicalSummary, + wasAdded = false, + wasUpdated = false, + shouldHeadsUpEver = false, + shouldHeadsUpAgain = false, + isAlerting = mHeadsUpManager.isAlerting(logicalSummary.key), + isBinding = isEntryBinding(logicalSummary), + ) + // If we transfer the alert and the summary isn't even attached, that means we + // should ensure the summary is no longer alerting, so we remove it here. + handlePostedEntry(summaryUpdateForRemoval, "detached-summary-remove-alert") + } else if (summaryUpdate!=null) { + mLogger.logPostedEntryWillNotEvaluate(summaryUpdate, "attached-summary-transferred") + } + + // Handle all posted entries -- if the child receiving the parent's alert is in the + // list, then set its flags to ensure it alerts. + var didAlertChildToReceiveParentAlert = false + postedEntries.asSequence() + .filter { it.key != logicalSummary.key } + .forEach { postedEntry -> + if (childToReceiveParentAlert.key == postedEntry.key) { + // Update the child's posted update so that it + postedEntry.shouldHeadsUpEver = true + postedEntry.shouldHeadsUpAgain = true + handlePostedEntry(postedEntry, "child-alert-transfer-target-$targetType") + didAlertChildToReceiveParentAlert = true + } else { + handlePostedEntry(postedEntry, "child-alert-non-target") + } + } + + // If the child receiving the alert was not updated on this tick (which can happen in a + // standard alert transfer scenario), then construct an update so that we can apply it. + if (!didAlertChildToReceiveParentAlert) { + val posted = PostedEntry( + childToReceiveParentAlert, + wasAdded = false, + wasUpdated = false, + shouldHeadsUpEver = true, + shouldHeadsUpAgain = true, + isAlerting = mHeadsUpManager.isAlerting(childToReceiveParentAlert.key), + isBinding = isEntryBinding(childToReceiveParentAlert), + ) + handlePostedEntry(posted, "non-posted-child-alert-transfer-target-$targetType") + } + } + // After this method runs, all posted entries should have been handled (or skipped). + mPostedEntries.clear() + } + + /** + * Find the posted child with the newest when, and return it if it is isolated and has + * GROUP_ALERT_SUMMARY so that it can be alerted. + */ + private fun findAlertOverride( + postedEntries: List<PostedEntry>, + locationLookupByKey: (String) -> GroupLocation, + ): NotificationEntry? = postedEntries.asSequence() + .filter { posted -> !posted.entry.sbn.notification.isGroupSummary } + .sortedBy { posted -> -posted.entry.sbn.notification.`when` } + .firstOrNull() + ?.let { posted -> + posted.entry.takeIf { entry -> + locationLookupByKey(entry.key) == GroupLocation.Isolated + && entry.sbn.notification.groupAlertBehavior == GROUP_ALERT_SUMMARY + } + } + + /** + * Of children which are attached, look for the child to receive the notification: + * First prefer children which were updated, then looking for the ones with the newest 'when' + */ + private fun findBestTransferChild( + logicalMembers: List<NotificationEntry>, + locationLookupByKey: (String) -> GroupLocation, + ): NotificationEntry? = logicalMembers.asSequence() + .filter { !it.sbn.notification.isGroupSummary } + .filter { locationLookupByKey(it.key) != GroupLocation.Detached } + .sortedWith(compareBy( + { !mPostedEntries.contains(it.key) }, + { -it.sbn.notification.`when` }, + )) + .firstOrNull() + + private fun getGroupLocationsByKey(list: List<ListEntry>): Map<String, GroupLocation> = + mutableMapOf<String, GroupLocation>().also { map -> + list.forEach { topLevelEntry -> + when (topLevelEntry) { + is NotificationEntry -> map[topLevelEntry.key] = GroupLocation.Isolated + is GroupEntry -> { + topLevelEntry.summary?.let { summary -> + map[summary.key] = GroupLocation.Summary + } + topLevelEntry.children.forEach { child -> + map[child.key] = GroupLocation.Child + } + } + else -> error("unhandled type $topLevelEntry") + } + } + } + + private val mPostedEntries = LinkedHashMap<String, PostedEntry>() + + fun handlePostedEntry(posted: PostedEntry, scenario: String) { + mLogger.logPostedEntryWillEvaluate(posted, scenario) + if (posted.wasAdded) { + if (posted.shouldHeadsUpEver) { + bindForAsyncHeadsUp(posted) + } + } else { + if (posted.isHeadsUpAlready) { + // NOTE: This might be because we're alerting (i.e. tracked by HeadsUpManager) OR + // it could be because we're binding, and that will affect the next step. + if (posted.shouldHeadsUpEver) { + // If alerting, we need to post an update. Otherwise we're still binding, + // and we can just let that finish. + if (posted.isAlerting) { + mHeadsUpManager.updateNotification(posted.key, posted.shouldHeadsUpAgain) + } + } else { + if (posted.isAlerting) { + // We don't want this to be interrupting anymore, let's remove it + mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/) + } else { + // Don't let the bind finish + cancelHeadsUpBind(posted.entry) + } + } + } else if (posted.shouldHeadsUpEver && posted.shouldHeadsUpAgain) { + // This notification was updated to be heads up, show it! + bindForAsyncHeadsUp(posted) + } + } + } + + private fun cancelHeadsUpBind(entry: NotificationEntry) { + mEntriesBindingUntil.remove(entry.key) + mHeadsUpViewBinder.abortBindCallback(entry) + } + + private fun bindForAsyncHeadsUp(posted: PostedEntry) { + // TODO: Add a guarantee to bindHeadsUpView of some kind of callback if the bind is + // cancelled so that we don't need to have this sad timeout hack. + mEntriesBindingUntil[posted.key] = mNow + BIND_TIMEOUT + mHeadsUpViewBinder.bindHeadsUpView(posted.entry, this::onHeadsUpViewBound) } private val mNotifCollectionListener = object : NotifCollectionListener { @@ -82,9 +342,17 @@ class HeadsUpCoordinator @Inject constructor( * Notification was just added and if it should heads up, bind the view and then show it. */ override fun onEntryAdded(entry: NotificationEntry) { - if (mNotificationInterruptStateProvider.shouldHeadsUp(entry)) { - mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) } - } + // shouldHeadsUp includes check for whether this notification should be filtered + val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry) + mPostedEntries[entry.key] = PostedEntry( + entry, + wasAdded = true, + wasUpdated = false, + shouldHeadsUpEver = shouldHeadsUpEver, + shouldHeadsUpAgain = true, + isAlerting = false, + isBinding = false, + ) } /** @@ -93,22 +361,26 @@ class HeadsUpCoordinator @Inject constructor( * up again. */ override fun onEntryUpdated(entry: NotificationEntry) { - val hunAgain = HeadsUpController.alertAgain(entry, entry.sbn.notification) - // includes check for whether this notification should be filtered: - val shouldHeadsUp = mNotificationInterruptStateProvider.shouldHeadsUp(entry) - val wasHeadsUp = mHeadsUpManager.isAlerting(entry.key) - if (wasHeadsUp) { - if (shouldHeadsUp) { - mHeadsUpManager.updateNotification(entry.key, hunAgain) - } else { - // We don't want this to be interrupting anymore, let's remove it - mHeadsUpManager.removeNotification( - entry.key, false /* removeImmediately */ - ) - } - } else if (shouldHeadsUp && hunAgain) { - // This notification was updated to be heads up, show it! - mHeadsUpViewBinder.bindHeadsUpView(entry) { entry -> onHeadsUpViewBound(entry) } + val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry) + val shouldHeadsUpAgain = shouldHunAgain(entry) + val isAlerting = mHeadsUpManager.isAlerting(entry.key) + val isBinding = isEntryBinding(entry) + mPostedEntries.compute(entry.key) { _, value -> + value?.also { update -> + update.wasUpdated = true + update.shouldHeadsUpEver = update.shouldHeadsUpEver || shouldHeadsUpEver + update.shouldHeadsUpAgain = update.shouldHeadsUpAgain || shouldHeadsUpAgain + update.isAlerting = isAlerting + update.isBinding = isBinding + } ?: PostedEntry( + entry, + wasAdded = false, + wasUpdated = true, + shouldHeadsUpEver = shouldHeadsUpEver, + shouldHeadsUpAgain = shouldHeadsUpAgain, + isAlerting = isAlerting, + isBinding = isBinding, + ) } } @@ -116,8 +388,12 @@ class HeadsUpCoordinator @Inject constructor( * Stop alerting HUNs that are removed from the notification collection */ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { + mPostedEntries.remove(entry.key) + cancelHeadsUpBind(entry) val entryKey = entry.key if (mHeadsUpManager.isAlerting(entryKey)) { + // TODO: This should probably know the RemoteInputCoordinator's conditions, + // or otherwise reference that coordinator's state, rather than replicate its logic val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) && !NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY) mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput) @@ -129,6 +405,14 @@ class HeadsUpCoordinator @Inject constructor( } } + /** + * Checks whether an update for a notification warrants an alert for the user. + */ + private fun shouldHunAgain(entry: NotificationEntry): Boolean { + return (!entry.hasInterrupted() || + (entry.sbn.notification.flags and Notification.FLAG_ONLY_ALERT_ONCE) == 0) + } + private val mLifetimeExtender = object : NotifLifetimeExtender { override fun getName() = TAG @@ -164,11 +448,13 @@ class HeadsUpCoordinator @Inject constructor( private val mNotifPromoter = object : NotifPromoter(TAG) { override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean = - isCurrentlyShowingHun(entry) + isGoingToShowHunNoRetract(entry) } val sectioner = object : NotifSectioner("HeadsUp", BUCKET_HEADS_UP) { - override fun isInSection(entry: ListEntry): Boolean = isCurrentlyShowingHun(entry) + override fun isInSection(entry: ListEntry): Boolean = + // TODO: This check won't notice if a child of the group is going to HUN... + isGoingToShowHunNoRetract(entry) override fun getHeaderNodeController(): NodeController? = // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and mIncomingHeaderController @@ -186,7 +472,34 @@ class HeadsUpCoordinator @Inject constructor( private fun isSticky(entry: NotificationEntry) = mHeadsUpManager.isSticky(entry.key) - private fun isCurrentlyShowingHun(entry: ListEntry) = mHeadsUpManager.isAlerting(entry.key) + private fun isEntryBinding(entry: ListEntry): Boolean { + val bindingUntil = mEntriesBindingUntil[entry.key] + return bindingUntil != null && bindingUntil >= mNow + } + + /** + * Whether the notification is already alerting or binding so that it can imminently alert + */ + private fun isAttemptingToShowHun(entry: ListEntry) = + mHeadsUpManager.isAlerting(entry.key) || isEntryBinding(entry) + + /** + * Whether the notification is already alerting/binding per [isAttemptingToShowHun] OR if it + * has been updated so that it should alert this update. This method is permissive because it + * returns `true` even if the update would (in isolation of its group) cause the alert to be + * retracted. This is important for not retracting transferred group alerts. + */ + private fun isGoingToShowHunNoRetract(entry: ListEntry) = + mPostedEntries[entry.key]?.calculateShouldBeHeadsUpNoRetract ?: isAttemptingToShowHun(entry) + + /** + * If the notification has been updated, then whether it should HUN in isolation, otherwise + * defers to the already alerting/binding state of [isAttemptingToShowHun]. This method is + * strict because any update which would revoke the alert supersedes the current + * alerting/binding state. + */ + private fun isGoingToShowHunStrict(entry: ListEntry) = + mPostedEntries[entry.key]?.calculateShouldBeHeadsUpStrict ?: isAttemptingToShowHun(entry) private fun endNotifLifetimeExtensionIfExtended(entry: NotificationEntry) { if (mNotifsExtendingLifetime.remove(entry)) { @@ -196,5 +509,29 @@ class HeadsUpCoordinator @Inject constructor( companion object { private const val TAG = "HeadsUpCoordinator" + private const val BIND_TIMEOUT = 1000L } -}
\ No newline at end of file + + data class PostedEntry( + val entry: NotificationEntry, + val wasAdded: Boolean, + var wasUpdated: Boolean, + var shouldHeadsUpEver: Boolean, + var shouldHeadsUpAgain: Boolean, + var isAlerting: Boolean, + var isBinding: Boolean, + ) { + val key = entry.key + val isHeadsUpAlready: Boolean + get() = isAlerting || isBinding + val calculateShouldBeHeadsUpStrict: Boolean + get() = shouldHeadsUpEver && (wasAdded || shouldHeadsUpAgain || isHeadsUpAlready) + val calculateShouldBeHeadsUpNoRetract: Boolean + get() = isHeadsUpAlready || (shouldHeadsUpEver && (wasAdded || shouldHeadsUpAgain)) + } +} + +private enum class GroupLocation { Detached, Isolated, Summary, Child } + +private fun Map<String, GroupLocation>.getLocation(key: String): GroupLocation = + getOrDefault(key, GroupLocation.Detached) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt new file mode 100644 index 000000000000..204a494c32e8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt @@ -0,0 +1,62 @@ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.util.Log +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotificationHeadsUpLog +import javax.inject.Inject + +private const val TAG = "HeadsUpCoordinator" + +class HeadsUpCoordinatorLogger constructor( + private val buffer: LogBuffer, + private val verbose: Boolean, +) { + @Inject + constructor(@NotificationHeadsUpLog buffer: LogBuffer) : + this(buffer, Log.isLoggable(TAG, Log.VERBOSE)) + + fun logPostedEntryWillEvaluate(posted: HeadsUpCoordinator.PostedEntry, reason: String) { + if (!verbose) return + buffer.log(TAG, LogLevel.VERBOSE, { + str1 = posted.key + str2 = reason + bool1 = posted.shouldHeadsUpEver + bool2 = posted.shouldHeadsUpAgain + }, { + "will evaluate posted entry $str1:" + + " reason=$str2 shouldHeadsUpEver=$bool1 shouldHeadsUpAgain=$bool2" + }) + } + + fun logPostedEntryWillNotEvaluate(posted: HeadsUpCoordinator.PostedEntry, reason: String) { + if (!verbose) return + buffer.log(TAG, LogLevel.VERBOSE, { + str1 = posted.key + str2 = reason + }, { + "will not evaluate posted entry $str1: reason=$str2" + }) + } + + fun logEvaluatingGroups(numGroups: Int) { + if (!verbose) return + buffer.log(TAG, LogLevel.VERBOSE, { + int1 = numGroups + }, { + "evaluating groups for alert transfer: $int1" + }) + } + + fun logEvaluatingGroup(groupKey: String, numPostedEntries: Int, logicalGroupSize: Int) { + if (!verbose) return + buffer.log(TAG, LogLevel.VERBOSE, { + str1 = groupKey + int1 = numPostedEntries + int2 = logicalGroupSize + }, { + "evaluating group for alert transfer: $str1" + + " numPostedEntries=$int1 logicalGroupSize=$int2" + }) + } +} 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/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/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 7dc2e1949274..ce3e27c55f3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -565,6 +565,10 @@ public class AmbientState { } } + public float getDozeAmount() { + return mDozeAmount; + } + /** * Is the device fully awake, which is different from not tark at all when there are pulsing * notifications. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java deleted file mode 100644 index bd5b7d7df5b6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.stack; - -import android.animation.AnimatorListenerAdapter; -import android.content.Context; -import android.util.AttributeSet; - -import com.android.systemui.statusbar.notification.row.ExpandableView; - -/** - * Root view to insert Lock screen media controls into the notification stack. - */ -public class MediaContainerView extends ExpandableView { - - public MediaContainerView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public long performRemoveAnimation(long duration, long delay, float translationDirection, - boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { - return 0; - } - - @Override - public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear, - Runnable onEnd) { - // No animation, it doesn't need it, this would be local - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt new file mode 100644 index 000000000000..b8f28b5a60ea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.stack + +import android.animation.AnimatorListenerAdapter +import android.content.Context +import android.content.res.Configuration +import android.graphics.Canvas +import android.graphics.Path +import android.graphics.RectF +import android.util.AttributeSet +import com.android.systemui.R +import com.android.systemui.statusbar.notification.row.ExpandableView + +/** + * Root view to insert Lock screen media controls into the notification stack. + */ +class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) { + + var cornerRadius = 0f + var clipHeight = 0 + var clipRect = RectF() + var clipPath = Path() + + init { + setWillNotDraw(false) // Run onDraw after invalidate. + updateResources() + } + + override fun onConfigurationChanged(newConfig: Configuration?) { + super.onConfigurationChanged(newConfig) + updateResources() + } + + private fun updateResources() { + cornerRadius = context.resources + .getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat() + } + + public override fun updateClipping() { + if (clipHeight != actualHeight) { + clipHeight = actualHeight + } + invalidate() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + val bounds = canvas.clipBounds + bounds.bottom = clipHeight + clipRect.set(bounds) + + clipPath.reset() + clipPath.addRoundRect(clipRect, cornerRadius, cornerRadius, Path.Direction.CW) + canvas.clipPath(clipPath) + } + + + override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float, + isHeadsUpAnimation: Boolean, endLocation: Float, + onFinishedRunnable: Runnable?, + animationListener: AnimatorListenerAdapter?): Long { + return 0 + } + + override fun performAddAnimation(delay: Long, duration: Long, isHeadsUpAppear: Boolean, + onEnd: Runnable?) { + // No animation, it doesn't need it, this would be local + } +}
\ No newline at end of file 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 eff8af0fb4c3..9f6a81d3a9ee 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 @@ -202,6 +202,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private int mBottomMargin; private int mBottomInset = 0; private float mQsExpansionFraction; + private final int mSplitShadeMinContentHeight; /** * The algorithm which calculates the properties for our children @@ -583,6 +584,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable .getDefaultColor(); int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); + mSplitShadeMinContentHeight = res.getDimensionPixelSize( + R.dimen.nssl_split_shade_min_content_height); mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback, minHeight, maxHeight); mExpandHelper.setEventSource(this); @@ -1273,8 +1276,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param listenerNeedsAnimation does the listener need to animate? */ private void updateStackPosition(boolean listenerNeedsAnimation) { - // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD - float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition + final float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition + mAmbientState.getOverExpansion() - getCurrentOverScrollAmount(false /* top */); final float fraction = mAmbientState.getExpansionFraction(); @@ -1284,15 +1286,31 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mOnStackYChanged.accept(listenerNeedsAnimation); } if (mQsExpansionFraction <= 0) { - final float stackEndHeight = Math.max(0f, - getHeight() - getEmptyBottomMargin() - mTopPadding); - mAmbientState.setStackEndHeight(stackEndHeight); - mAmbientState.setStackHeight( - MathUtils.lerp(stackEndHeight * StackScrollAlgorithm.START_FRACTION, - stackEndHeight, fraction)); + final float endHeight = updateStackEndHeight( + getHeight(), getEmptyBottomMargin(), mTopPadding); + updateStackHeight(endHeight, fraction); } } + public float updateStackEndHeight(float height, float bottomMargin, float topPadding) { + final float stackEndHeight = Math.max(0f, height - bottomMargin - topPadding); + mAmbientState.setStackEndHeight(stackEndHeight); + return stackEndHeight; + } + + public void updateStackHeight(float endHeight, float fraction) { + // During the (AOD<=>LS) transition where dozeAmount is changing, + // apply dozeAmount to stack height instead of expansionFraction + // to unfurl notifications on AOD=>LS wakeup (and furl up on LS=>AOD sleep) + final float dozeAmount = mAmbientState.getDozeAmount(); + if (0f < dozeAmount && dozeAmount < 1f) { + fraction = 1f - dozeAmount; + } + mAmbientState.setStackHeight( + MathUtils.lerp(endHeight * StackScrollAlgorithm.START_FRACTION, + endHeight, fraction)); + } + /** * Add a listener when the StackY changes. The argument signifies whether an animation is * needed. @@ -3910,7 +3928,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.COORDINATOR) int getEmptyBottomMargin() { - return Math.max(mMaxLayoutHeight - mContentHeight, 0); + int contentHeight; + if (mShouldUseSplitNotificationShade) { + // When in split shade and there are no notifications, the height can be too low, as + // it is based on notifications bottom, which is lower on split shade. + // Here we prefer to use at least a minimum height defined for split shade. + // Otherwise the expansion motion is too fast. + contentHeight = Math.max(mSplitShadeMinContentHeight, mContentHeight); + } else { + contentHeight = mContentHeight; + } + return Math.max(mMaxLayoutHeight - contentHeight, 0); } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -5469,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/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 0d2bddcc8b77..89b5aef00656 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; 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 8d500fa4e8b0..04d3e9a01d86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static android.app.StatusBarManager.SESSION_KEYGUARD; + import android.annotation.IntDef; import android.content.Context; import android.content.res.Resources; @@ -28,7 +30,10 @@ import android.os.SystemClock; import android.os.Trace; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.InstanceId; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; @@ -48,6 +53,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationMediaManager; @@ -156,6 +162,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final DozeParameters mDozeParameters; private final KeyguardStateController mKeyguardStateController; private final NotificationShadeWindowController mNotificationShadeWindowController; + private final SessionTracker mSessionTracker; private final Context mContext; private final int mWakeUpDelay; private int mMode; @@ -273,7 +280,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp ScreenLifecycle screenLifecycle, AuthController authController, StatusBarStateController statusBarStateController, - KeyguardUnlockAnimationController keyguardUnlockAnimationController) { + KeyguardUnlockAnimationController keyguardUnlockAnimationController, + SessionTracker sessionTracker) { mContext = context; mPowerManager = powerManager; mShadeController = shadeController; @@ -297,6 +305,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mAuthController = authController; mStatusBarStateController = statusBarStateController; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; + mSessionTracker = sessionTracker; dumpManager.registerDumpable(getClass().getName(), this); } @@ -376,7 +385,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH) .setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType))); Optional.ofNullable(BiometricUiEvent.SUCCESS_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)) - .ifPresent(UI_EVENT_LOGGER::log); + .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId())); boolean unlockAllowed = mKeyguardStateController.isOccluded() @@ -641,7 +650,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH) .setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType))); Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)) - .ifPresent(UI_EVENT_LOGGER::log); + .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId())); if (biometricSourceType == BiometricSourceType.FINGERPRINT && mUpdateMonitor.isUdfpsSupported()) { @@ -656,7 +665,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (mNumConsecutiveFpFailures >= FP_ATTEMPTS_BEFORE_SHOW_BOUNCER) { startWakeAndUnlock(MODE_SHOW_BOUNCER); - UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN); + UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId()); mNumConsecutiveFpFailures = 0; } } @@ -670,7 +679,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp .setType(MetricsEvent.TYPE_ERROR).setSubtype(toSubtype(biometricSourceType)) .addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId)); Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType)) - .ifPresent(UI_EVENT_LOGGER::log); + .ifPresent(event -> UI_EVENT_LOGGER.log(event, getSessionId())); // if we're on the shade and we're locked out, immediately show the bouncer if (biometricSourceType == BiometricSourceType.FINGERPRINT @@ -680,7 +689,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp && (mStatusBarStateController.getState() == StatusBarState.SHADE || mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED)) { startWakeAndUnlock(MODE_SHOW_BOUNCER); - UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN); + UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId()); } cleanup(); } @@ -786,6 +795,9 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp return mBiometricType; } + private @Nullable InstanceId getSessionId() { + return mSessionTracker.getSessionId(SESSION_KEYGUARD); + } /** * Translates biometric source type for logging purpose. */ 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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index cfa24565c3ed..bb0ed95cc6ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -2484,7 +2484,6 @@ public class NotificationPanelViewController extends PanelViewController mSplitShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction); mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); mSplitShadeHeaderController.setShadeExpanded(mQsVisible); - mKeyguardStatusBarViewController.updateViewState(); if (mCommunalViewController != null) { mCommunalViewController.updateQsExpansion(qsExpansionFraction); @@ -4189,9 +4188,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/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/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..b833c894c43f 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; @@ -51,6 +52,7 @@ 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 +113,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() { @@ -235,6 +238,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb SysuiStatusBarStateController sysuiStatusBarStateController, ConfigurationController configurationController, KeyguardUpdateMonitor keyguardUpdateMonitor, + DreamOverlayStateController dreamOverlayStateController, NavigationModeController navigationModeController, DockManager dockManager, NotificationShadeWindowController notificationShadeWindowController, @@ -249,6 +253,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mConfigurationController = configurationController; mNavigationModeController = navigationModeController; mNotificationShadeWindowController = notificationShadeWindowController; + mDreamOverlayStateController = dreamOverlayStateController; mKeyguardStateController = keyguardStateController; mMediaManager = notificationMediaManager; mKeyguardUpdateManager = keyguardUpdateMonitor; @@ -370,6 +375,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 +383,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 +718,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 +818,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__HIDDEN); + Trace.endSection(); } private boolean needsBypassFading() { @@ -1174,7 +1183,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/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/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/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/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/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt index 6bc65054d830..aa671d1e3790 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt @@ -20,11 +20,12 @@ import android.hardware.biometrics.BiometricSourceType import org.mockito.Mockito.verify import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.internal.logging.InstanceId import com.android.internal.logging.UiEventLogger import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager +import com.android.systemui.log.SessionTracker import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -44,9 +45,11 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock - lateinit var dumpManager: DumpManager - @Mock lateinit var strongAuthTracker: KeyguardUpdateMonitor.StrongAuthTracker + @Mock + lateinit var sessionTracker: SessionTracker + @Mock + lateinit var sessionId: InstanceId @Captor lateinit var updateMonitorCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback> @@ -58,11 +61,12 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) whenever(keyguardUpdateMonitor.strongAuthTracker).thenReturn(strongAuthTracker) + whenever(sessionTracker.getSessionId(anyInt())).thenReturn(sessionId) keyguardBiometricLockoutLogger = KeyguardBiometricLockoutLogger( mContext, uiEventLogger, keyguardUpdateMonitor, - dumpManager) + sessionTracker) } @Test @@ -76,7 +80,7 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { // THEN encrypted / lockdown state is logged verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent - .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN) + .PRIMARY_AUTH_REQUIRED_ENCRYPTED_OR_LOCKDOWN, sessionId) } @Test @@ -93,7 +97,7 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { // THEN primary auth required state is logged verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent - .PRIMARY_AUTH_REQUIRED_TIMEOUT) + .PRIMARY_AUTH_REQUIRED_TIMEOUT, sessionId) } @Test @@ -110,7 +114,7 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { // THEN primary auth required state is logged verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent - .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE) + .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE, sessionId) } @Test @@ -128,9 +132,9 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { // THEN primary auth required state is logged with all the reasons verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent - .PRIMARY_AUTH_REQUIRED_TIMEOUT) + .PRIMARY_AUTH_REQUIRED_TIMEOUT, sessionId) verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent - .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE) + .PRIMARY_AUTH_REQUIRED_UNATTENDED_UPDATE, sessionId) // WHEN onStrongAuthStateChanged is called again updateMonitorCallback.onStrongAuthStateChanged(0) @@ -152,7 +156,7 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { // THEN primary auth required state is logged verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent - .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT) + .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT, sessionId) // WHEN face lockout is reset whenever(keyguardUpdateMonitor.isFaceLockedOut).thenReturn(false) @@ -160,7 +164,7 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { // THEN primary auth required state is logged verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent - .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET) + .PRIMARY_AUTH_REQUIRED_FACE_LOCKED_OUT_RESET, sessionId) } @Test @@ -176,7 +180,7 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { // THEN primary auth required state is logged verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent - .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT) + .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT, sessionId) // WHEN fingerprint lockout is reset whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) @@ -184,7 +188,7 @@ class KeyguardBiometricLockoutLoggerTest : SysuiTestCase() { // THEN primary auth required state is logged verify(uiEventLogger).log(KeyguardBiometricLockoutLogger.PrimaryAuthRequiredEvent - .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET) + .PRIMARY_AUTH_REQUIRED_FINGERPRINT_LOCKED_OUT_RESET, sessionId) } fun captureUpdateMonitorCallback() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 599e5474c564..a819a7a0f815 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -49,6 +49,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -120,6 +121,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { private FeatureFlags mFeatureFlags; @Mock private UserSwitcherController mUserSwitcherController; + @Mock + private SessionTracker mSessionTracker; private Configuration mConfiguration; private KeyguardSecurityContainerController mKeyguardSecurityContainerController; @@ -154,7 +157,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger, mKeyguardStateController, mKeyguardSecurityViewFlipperController, mConfigurationController, mFalsingCollector, mFalsingManager, - mUserSwitcherController, mFeatureFlags, mGlobalSettings).create(mSecurityCallback); + mUserSwitcherController, mFeatureFlags, mGlobalSettings, + mSessionTracker).create(mSecurityCallback); } @Test 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/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/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt new file mode 100644 index 000000000000..2cb19393d2c6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.dump + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.LogcatEchoTracker + +/** + * Creates a LogBuffer that will echo everything to logcat, which is useful for debugging tests. + */ +fun logcatLogBuffer(name: String = "EchoToLogcatLogBuffer") = + LogBuffer(name, 50, 50, LogcatEchoTrackerAlways()) + +/** + * A [LogcatEchoTracker] that always allows echoing to the logcat. + */ +class LogcatEchoTrackerAlways : LogcatEchoTracker { + override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = true + override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true +} 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/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/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/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/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 2e84482496ca..8fb066bb4a39 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -368,6 +368,50 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + public void testGroupsWhoLoseAllChildrenToPromotionSuppressSummary() { + // GIVEN a group with two children + addGroupChild(0, PACKAGE_2, GROUP_1); + addGroupSummary(1, PACKAGE_2, GROUP_1); + addGroupChild(2, PACKAGE_2, GROUP_1); + + // GIVEN a promoter that will promote one of children to top level + mListBuilder.addPromoter(new IdPromoter(0, 2)); + + // WHEN we build the list + dispatchBuild(); + + // THEN both children end up at top level (because group is now too small) + verifyBuiltList( + notif(0), + notif(2) + ); + + // THEN the summary is discarded + assertNull(mEntrySet.get(1).getParent()); + } + + @Test + public void testGroupsWhoLoseOnlyChildToPromotionSuppressSummary() { + // GIVEN a group with two children + addGroupChild(0, PACKAGE_2, GROUP_1); + addGroupSummary(1, PACKAGE_2, GROUP_1); + + // GIVEN a promoter that will promote one of children to top level + mListBuilder.addPromoter(new IdPromoter(0)); + + // WHEN we build the list + dispatchBuild(); + + // THEN both children end up at top level (because group is now too small) + verifyBuiltList( + notif(0) + ); + + // THEN the summary is discarded + assertNull(mEntrySet.get(1).getParent()); + } + + @Test public void testPreviousParentsAreSetProperly() { // GIVEN a notification that is initially added to the list PackageFilter filter = new PackageFilter(PACKAGE_2); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index c67a2331b023..144eefb17283 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -15,14 +15,20 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator +import android.app.Notification.GROUP_ALERT_ALL +import android.app.Notification.GROUP_ALERT_SUMMARY import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.statusbar.NotificationRemoteInputManager +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener @@ -32,6 +38,7 @@ import com.android.systemui.statusbar.notification.collection.render.NodeControl import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback +import com.android.systemui.statusbar.phone.NotificationGroupTestHelper import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener import com.android.systemui.util.concurrency.FakeExecutor @@ -64,10 +71,13 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { private lateinit var mCollectionListener: NotifCollectionListener private lateinit var mNotifPromoter: NotifPromoter private lateinit var mNotifLifetimeExtender: NotifLifetimeExtender + private lateinit var mBeforeTransformGroupsListener: OnBeforeTransformGroupsListener + private lateinit var mBeforeFinalizeFilterListener: OnBeforeFinalizeFilterListener private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener private lateinit var mNotifSectioner: NotifSectioner private val mNotifPipeline: NotifPipeline = mock() + private val mLogger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true) private val mHeadsUpManager: HeadsUpManager = mock() private val mHeadsUpViewBinder: HeadsUpViewBinder = mock() private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider = mock() @@ -76,12 +86,24 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { private val mHeaderController: NodeController = mock() private lateinit var mEntry: NotificationEntry - private val mExecutor = FakeExecutor(FakeSystemClock()) + private lateinit var mGroupSummary: NotificationEntry + private lateinit var mGroupPriority: NotificationEntry + private lateinit var mGroupSibling1: NotificationEntry + private lateinit var mGroupSibling2: NotificationEntry + private lateinit var mGroupChild1: NotificationEntry + private lateinit var mGroupChild2: NotificationEntry + private lateinit var mGroupChild3: NotificationEntry + private val mSystemClock = FakeSystemClock() + private val mExecutor = FakeExecutor(mSystemClock) private val mHuns: ArrayList<NotificationEntry> = ArrayList() + private lateinit var mHelper: NotificationGroupTestHelper @Before fun setUp() { MockitoAnnotations.initMocks(this) + mHelper = NotificationGroupTestHelper(mContext) mCoordinator = HeadsUpCoordinator( + mLogger, + mSystemClock, mHeadsUpManager, mHeadsUpViewBinder, mNotificationInterruptStateProvider, @@ -100,6 +122,12 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { mNotifLifetimeExtender = withArgCaptor { verify(mNotifPipeline).addNotificationLifetimeExtender(capture()) } + mBeforeTransformGroupsListener = withArgCaptor { + verify(mNotifPipeline).addOnBeforeTransformGroupsListener(capture()) + } + mBeforeFinalizeFilterListener = withArgCaptor { + verify(mNotifPipeline).addOnBeforeFinalizeFilterListener(capture()) + } mOnHeadsUpChangedListener = withArgCaptor { verify(mHeadsUpManager).addListener(capture()) } @@ -116,6 +144,16 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { mNotifSectioner = mCoordinator.sectioner mNotifLifetimeExtender.setCallback(mEndLifetimeExtension) mEntry = NotificationEntryBuilder().build() + // Same summary we can use for either set of children + mGroupSummary = mHelper.createSummaryNotification(GROUP_ALERT_ALL, 0, "summary", 500) + // One set of children with GROUP_ALERT_SUMMARY + mGroupPriority = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 0, "priority", 400) + mGroupSibling1 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 1, "sibling", 300) + mGroupSibling2 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 2, "sibling", 200) + // Another set of children with GROUP_ALERT_ALL + mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350) + mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250) + mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150) } @Test @@ -156,6 +194,34 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { } @Test + fun testPromotesAddedHUN() { + // GIVEN the current entry should heads up + whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true) + + // WHEN the notification is added but not yet binding + mCollectionListener.onEntryAdded(mEntry) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any()) + + // THEN only promote mEntry + assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)) + } + + @Test + fun testPromotesBindingHUN() { + // GIVEN the current entry should heads up + whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true) + + // WHEN the notification started binding on the previous run + mCollectionListener.onEntryAdded(mEntry) + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), any()) + + // THEN only promote mEntry + assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)) + } + + @Test fun testPromotesCurrentHUN() { // GIVEN the current HUN is set to mEntry addHUN(mEntry) @@ -198,6 +264,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true) mCollectionListener.onEntryAdded(mEntry) + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + verify(mHeadsUpManager, never()).showNotification(mEntry) withArgCaptor<BindCallback> { verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), capture()) }.onBindFinished(mEntry) @@ -211,6 +280,8 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { // WHEN a notification shouldn't HUN and its inflation is finished whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false) mCollectionListener.onEntryAdded(mEntry) + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) // THEN we never bind the heads up view or tell HeadsUpManager to show the notification verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any()) @@ -235,4 +306,296 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { whenever(mHeadsUpManager.topEntry).thenReturn(entry) mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true) } -}
\ No newline at end of file + + @Test + fun testTransferIsolatedChildAlert_withGroupAlertSummary() { + setShouldHeadsUp(mGroupSummary) + whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupSibling1)) + + mCollectionListener.onEntryAdded(mGroupSummary) + mCollectionListener.onEntryAdded(mGroupSibling1) + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupSibling1)) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupSibling1)) + + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) + finishBind(mGroupSibling1) + + verify(mHeadsUpManager, never()).showNotification(mGroupSummary) + verify(mHeadsUpManager).showNotification(mGroupSibling1) + } + + @Test + fun testTransferIsolatedChildAlert_withGroupAlertAll() { + setShouldHeadsUp(mGroupSummary) + whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupChild1)) + + mCollectionListener.onEntryAdded(mGroupSummary) + mCollectionListener.onEntryAdded(mGroupChild1) + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupChild1)) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupChild1)) + + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) + finishBind(mGroupChild1) + + verify(mHeadsUpManager, never()).showNotification(mGroupSummary) + verify(mHeadsUpManager).showNotification(mGroupChild1) + } + + @Test + fun testTransferTwoIsolatedChildAlert_withGroupAlertSummary() { + // WHEN a notification should HUN and its inflation is finished + setShouldHeadsUp(mGroupSummary) + whenever(mNotifPipeline.allNotifs) + .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority)) + + mCollectionListener.onEntryAdded(mGroupSummary) + mCollectionListener.onEntryAdded(mGroupSibling1) + mCollectionListener.onEntryAdded(mGroupSibling2) + val entryList = listOf(mGroupSibling1, mGroupSibling2) + mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList) + + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) + finishBind(mGroupSibling1) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) + + // THEN we tell the HeadsUpManager to show the notification + verify(mHeadsUpManager, never()).showNotification(mGroupSummary) + verify(mHeadsUpManager).showNotification(mGroupSibling1) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + } + + @Test + fun testTransferTwoIsolatedChildAlert_withGroupAlertAll() { + // WHEN a notification should HUN and its inflation is finished + setShouldHeadsUp(mGroupSummary) + whenever(mNotifPipeline.allNotifs) + .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2, mGroupPriority)) + + mCollectionListener.onEntryAdded(mGroupSummary) + mCollectionListener.onEntryAdded(mGroupChild1) + mCollectionListener.onEntryAdded(mGroupChild2) + val entryList = listOf(mGroupChild1, mGroupChild2) + mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList) + + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) + finishBind(mGroupChild1) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any()) + + // THEN we tell the HeadsUpManager to show the notification + verify(mHeadsUpManager, never()).showNotification(mGroupSummary) + verify(mHeadsUpManager).showNotification(mGroupChild1) + verify(mHeadsUpManager, never()).showNotification(mGroupChild2) + } + + @Test + fun testTransferToPriorityOnAddWithTwoSiblings() { + // WHEN a notification should HUN and its inflation is finished + setShouldHeadsUp(mGroupSummary) + whenever(mNotifPipeline.allNotifs) + .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority)) + + mCollectionListener.onEntryAdded(mGroupSummary) + mCollectionListener.onEntryAdded(mGroupPriority) + mCollectionListener.onEntryAdded(mGroupSibling1) + mCollectionListener.onEntryAdded(mGroupSibling2) + + val beforeTransformGroup = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2)) + .build() + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + + val afterTransformGroup = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupSibling1, mGroupSibling2)) + .build() + mBeforeFinalizeFilterListener + .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup)) + + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) + finishBind(mGroupPriority) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any()) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) + + // THEN we tell the HeadsUpManager to show the notification + verify(mHeadsUpManager, never()).showNotification(mGroupSummary) + verify(mHeadsUpManager).showNotification(mGroupPriority) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + } + + @Test + fun testTransferToPriorityOnUpdateWithTwoSiblings() { + setShouldHeadsUp(mGroupSummary) + whenever(mNotifPipeline.allNotifs) + .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority)) + + mCollectionListener.onEntryUpdated(mGroupSummary) + mCollectionListener.onEntryUpdated(mGroupPriority) + mCollectionListener.onEntryUpdated(mGroupSibling1) + mCollectionListener.onEntryUpdated(mGroupSibling2) + + val beforeTransformGroup = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2)) + .build() + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + + val afterTransformGroup = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupSibling1, mGroupSibling2)) + .build() + mBeforeFinalizeFilterListener + .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup)) + + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) + finishBind(mGroupPriority) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any()) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) + + verify(mHeadsUpManager, never()).showNotification(mGroupSummary) + verify(mHeadsUpManager).showNotification(mGroupPriority) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + } + + @Test + fun testTransferToPriorityOnUpdateWithTwoNonUpdatedSiblings() { + setShouldHeadsUp(mGroupSummary) + whenever(mNotifPipeline.allNotifs) + .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority)) + + mCollectionListener.onEntryUpdated(mGroupSummary) + mCollectionListener.onEntryUpdated(mGroupPriority) + + val beforeTransformGroup = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2)) + .build() + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + + val afterTransformGroup = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupSibling1, mGroupSibling2)) + .build() + mBeforeFinalizeFilterListener + .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup)) + + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) + finishBind(mGroupPriority) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any()) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) + + verify(mHeadsUpManager, never()).showNotification(mGroupSummary) + verify(mHeadsUpManager).showNotification(mGroupPriority) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + } + + @Test + fun testNoTransferToPriorityOnUpdateOfTwoSiblings() { + setShouldHeadsUp(mGroupSummary) + whenever(mNotifPipeline.allNotifs) + .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority)) + + mCollectionListener.onEntryUpdated(mGroupSummary) + mCollectionListener.onEntryUpdated(mGroupSibling1) + mCollectionListener.onEntryUpdated(mGroupSibling2) + + val beforeTransformGroup = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2)) + .build() + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + + val afterTransformGroup = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupSibling1, mGroupSibling2)) + .build() + mBeforeFinalizeFilterListener + .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup)) + + finishBind(mGroupSummary) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupPriority), any()) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any()) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) + + verify(mHeadsUpManager).showNotification(mGroupSummary) + verify(mHeadsUpManager, never()).showNotification(mGroupPriority) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + } + + @Test + fun testNoTransferTwoChildAlert_withGroupAlertSummary() { + setShouldHeadsUp(mGroupSummary) + whenever(mNotifPipeline.allNotifs) + .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2)) + + mCollectionListener.onEntryAdded(mGroupSummary) + mCollectionListener.onEntryAdded(mGroupSibling1) + mCollectionListener.onEntryAdded(mGroupSibling2) + val groupEntry = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupSibling1, mGroupSibling2)) + .build() + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + + finishBind(mGroupSummary) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any()) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) + + verify(mHeadsUpManager).showNotification(mGroupSummary) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) + verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + } + + @Test + fun testNoTransferTwoChildAlert_withGroupAlertAll() { + setShouldHeadsUp(mGroupSummary) + whenever(mNotifPipeline.allNotifs) + .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2)) + + mCollectionListener.onEntryAdded(mGroupSummary) + mCollectionListener.onEntryAdded(mGroupChild1) + mCollectionListener.onEntryAdded(mGroupChild2) + val groupEntry = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupChild1, mGroupChild2)) + .build() + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + + finishBind(mGroupSummary) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild1), any()) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any()) + + verify(mHeadsUpManager).showNotification(mGroupSummary) + verify(mHeadsUpManager, never()).showNotification(mGroupChild1) + verify(mHeadsUpManager, never()).showNotification(mGroupChild2) + } + + private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) { + whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should) + } + + private fun finishBind(entry: NotificationEntry) { + verify(mHeadsUpManager, never()).showNotification(entry) + withArgCaptor<BindCallback> { + verify(mHeadsUpViewBinder).bindHeadsUpView(eq(entry), capture()) + }.onBindFinished(entry) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt new file mode 100644 index 000000000000..0909ff2e5993 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt @@ -0,0 +1,38 @@ +package com.android.systemui.statusbar.notification.stack + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.LayoutInflater +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.R +import junit.framework.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Tests for {@link MediaContainView}. + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class MediaContainerViewTest : SysuiTestCase() { + + lateinit var mediaContainerView : MediaContainerView + + @Before + fun setUp() { + mediaContainerView = LayoutInflater.from(context).inflate( + R.layout.keyguard_media_container, null, false) as MediaContainerView + } + + @Test + fun testUpdateClipping_updatesClipHeight() { + assertTrue(mediaContainerView.clipHeight == 0) + + mediaContainerView.actualHeight = 10 + mediaContainerView.updateClipping() + assertTrue(mediaContainerView.clipHeight == 10) + } +}
\ No newline at end of file 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/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 46ba09795143..bdcbbbc99ea3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -27,6 +27,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; @@ -44,6 +45,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.MathUtils; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; @@ -165,6 +167,47 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test + public void testUpdateStackEndHeight_forEndOfStackHeightAnimation() { + final float nsslHeight = 10f; + final float bottomMargin = 1f; + final float topPadding = 1f; + + mStackScroller.updateStackEndHeight(nsslHeight, bottomMargin, topPadding); + final float stackEndHeight = nsslHeight - bottomMargin - topPadding; + assertTrue(mAmbientState.getStackEndHeight() == stackEndHeight); + } + + @Test + public void testUpdateStackHeight_withDozeAmount_whenDozeChanging() { + final float dozeAmount = 0.5f; + mAmbientState.setDozeAmount(dozeAmount); + + final float endHeight = 8f; + final float expansionFraction = 1f; + float expected = MathUtils.lerp( + endHeight * StackScrollAlgorithm.START_FRACTION, + endHeight, dozeAmount); + + mStackScroller.updateStackHeight(endHeight, expansionFraction); + assertTrue(mAmbientState.getStackHeight() == expected); + } + + @Test + public void testUpdateStackHeight_withExpansionAmount_whenDozeNotChanging() { + final float dozeAmount = 1f; + mAmbientState.setDozeAmount(dozeAmount); + + final float endHeight = 8f; + final float expansionFraction = 0.5f; + final float expected = MathUtils.lerp( + endHeight * StackScrollAlgorithm.START_FRACTION, + endHeight, expansionFraction); + + mStackScroller.updateStackHeight(endHeight, expansionFraction); + assertTrue(mAmbientState.getStackHeight() == expected); + } + + @Test public void testNotDimmedOnKeyguard() { when(mBarState.getState()).thenReturn(StatusBarState.SHADE); mStackScroller.setDimmed(true /* dimmed */, false /* animate */); 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 5ca1f21eb021..8c7d22dde0b7 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 @@ -46,6 +46,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -107,6 +108,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private StatusBarStateController mStatusBarStateController; @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; + @Mock + private SessionTracker mSessionTracker; private BiometricUnlockController mBiometricUnlockController; @Before @@ -129,7 +132,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mUpdateMonitor, res.getResources(), mKeyguardBypassController, mDozeParameters, mMetricsLogger, mDumpManager, mPowerManager, mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle, - mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController); + mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController, + mSessionTracker); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java index ac32b9d6f01a..47f15a1720a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java @@ -59,6 +59,13 @@ public final class NotificationGroupTestHelper { return createEntry(id, tag, true, groupAlertBehavior); } + public NotificationEntry createSummaryNotification( + int groupAlertBehavior, int id, String tag, long when) { + NotificationEntry entry = createSummaryNotification(groupAlertBehavior, id, tag); + entry.getSbn().getNotification().when = when; + return entry; + } + public NotificationEntry createChildNotification() { return createChildNotification(Notification.GROUP_ALERT_ALL); } @@ -71,6 +78,13 @@ public final class NotificationGroupTestHelper { return createEntry(id, tag, false, groupAlertBehavior); } + public NotificationEntry createChildNotification( + int groupAlertBehavior, int id, String tag, long when) { + NotificationEntry entry = createChildNotification(groupAlertBehavior, id, tag); + entry.getSbn().getNotification().when = when; + return entry; + } + public NotificationEntry createEntry(int id, String tag, boolean isSummary, int groupAlertBehavior) { Notification notif = new Notification.Builder(mContext, TEST_CHANNEL_ID) 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..bb79941b0e53 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 @@ -43,6 +43,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 +98,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { private KeyguardMessageArea mKeyguardMessageArea; @Mock private ShadeController mShadeController; + @Mock + private DreamOverlayStateController mDreamOverlayStateController; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -116,6 +119,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mStatusBarStateController, mock(ConfigurationController.class), mKeyguardUpdateMonitor, + mDreamOverlayStateController, mock(NavigationModeController.class), mock(DockManager.class), mock(NotificationShadeWindowController.class), 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/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/Android.bp b/services/Android.bp index af70692a88e5..7e009868fa1a 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -95,7 +95,6 @@ filegroup { ":services.selectiontoolbar-sources", ":services.smartspace-sources", ":services.speech-sources", - ":services.startop.iorap-sources", ":services.systemcaptions-sources", ":services.translation-sources", ":services.texttospeech-sources", @@ -151,7 +150,6 @@ java_library { "services.selectiontoolbar", "services.smartspace", "services.speech", - "services.startop", "services.systemcaptions", "services.translation", "services.texttospeech", @@ -207,7 +205,6 @@ stubs_defaults { " --hide-annotation android.annotation.Hide" + " --hide InternalClasses" + // com.android.* classes are okay in this interface // TODO: remove the --hide options below - " --hide-package com.google.android.startop.iorap" + " --hide DeprecationMismatch" + " --hide HiddenTypedefConstant", visibility: ["//frameworks/base:__subpackages__"], 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/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index aa42e8deb581..1cff3744687e 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -284,7 +284,9 @@ final class AutofillManagerServiceImpl } final Session session = mSessions.get(sessionId); if (session != null && uid == session.uid) { - session.setAuthenticationResultLocked(data, authenticationId); + synchronized (session.mLock) { + session.setAuthenticationResultLocked(data, authenticationId); + } } } @@ -374,7 +376,9 @@ final class AutofillManagerServiceImpl + " hc=" + hasCallback + " f=" + flags + " aa=" + forAugmentedAutofillOnly; mMaster.logRequestLocked(historyItem); - newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags); + synchronized (newSession.mLock) { + newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags); + } if (forAugmentedAutofillOnly) { // Must embed the flag in the response, at the high-end side of the long. diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index a4bf52a3ed1b..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,8 @@ 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; import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED; @@ -46,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; @@ -147,12 +152,16 @@ 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; + private final AutofillManagerServiceImpl mService; private final Handler mHandler; - private final Object mLock; private final AutoFillUI mUi; + @NonNull private final Context mContext; private final MetricsLogger mMetricsLogger = new MetricsLogger(); @@ -267,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. @@ -354,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 @@ -406,28 +447,25 @@ 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. */ + private boolean mFillDialogDisabled; } /** @@ -441,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) { @@ -467,6 +507,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingInlineSuggestionsRequest = inlineRequest; } + @GuardedBy("mLock") void maybeRequestFillFromServiceLocked() { if (mPendingFillRequest == null) { return; @@ -484,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; @@ -588,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(); @@ -604,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. @@ -860,7 +971,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.getRemoteInlineSuggestionRenderServiceLocked(); if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled) && remoteRenderService != null - && isViewFocusedLocked(flags)) { + && (isViewFocusedLocked(flags) || (isRequestFromActivityStarted(flags)))) { final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer; if (mSessionFlags.mClientSuggestionsEnabled) { final int finalRequestId = requestId; @@ -906,6 +1017,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState requestAssistStructureLocked(requestId, flags); } + private boolean isRequestFromActivityStarted(int flags) { + return (flags & FLAG_ACTIVITY_START) != 0; + } + @GuardedBy("mLock") private void requestAssistStructureLocked(int requestId, int flags) { try { @@ -954,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; @@ -1086,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); @@ -1196,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"); @@ -1501,6 +1624,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState this, intentSender, intent)); } + // AutoFillUiCallback + @Override + public void requestShowSoftInput(AutofillId id) { + IAutoFillManagerClient client = getClient(); + if (client != null) { + try { + client.requestShowSoftInput(id); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending input show up notification", e); + } + } + synchronized (mLock) { + // stop to show fill dialog + mSessionFlags.mFillDialogDisabled = true; + } + } + private void notifyFillUiHidden(@NonNull AutofillId autofillId) { synchronized (mLock) { try { @@ -2869,6 +3009,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // View is triggering autofill. mCurrentViewId = viewState.id; viewState.update(value, virtualBounds, flags); + if (!isRequestFromActivityStarted(flags)) { + mSessionFlags.mFillDialogDisabled = true; + } requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags); break; case ACTION_VALUE_CHANGED: @@ -2958,6 +3101,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (isSameViewEntered) { + setFillDialogDisabledAndStartInput(); return; } @@ -2968,6 +3112,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (Objects.equals(mCurrentViewId, viewState.id)) { if (sVerbose) Slog.v(TAG, "Exiting view " + id); mUi.hideFillUi(this); + mUi.hideFillDialog(this); hideAugmentedAutofillLocked(viewState); // We don't send an empty response to IME so that it doesn't cause UI flicker // on the IME side if it arrives before the input view is finished on the IME. @@ -3148,6 +3293,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } + if (requestShowFillDialog(response, filledId, filterText)) { + synchronized (mLock) { + final ViewState currentView = mViewStates.get(mCurrentViewId); + currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN); + mService.logDatasetShown(id, mClientState); + } + return; + } + + setFillDialogDisabled(); + if (response.supportsInlineSuggestions()) { synchronized (mLock) { if (requestShowInlineSuggestionsLocked(response, filterText)) { @@ -3192,6 +3348,81 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + @GuardedBy("mLock") + private void updateFillDialogTriggerIdsLocked() { + final FillResponse response = getLastResponseLocked(null); + + if (response == null) return; + + final AutofillId[] ids = response.getFillDialogTriggerIds(); + notifyClientFillDialogTriggerIds(ids == null ? null : Arrays.asList(ids)); + } + + private void notifyClientFillDialogTriggerIds(List<AutofillId> fieldIds) { + try { + if (sVerbose) { + Slog.v(TAG, "notifyFillDialogTriggerIds(): " + fieldIds); + } + getClient().notifyFillDialogTriggerIds(fieldIds); + } catch (RemoteException e) { + Slog.w(TAG, "Cannot set trigger ids for fill dialog", e); + } + } + + private boolean isFillDialogUiEnabled() { + // TODO read from Settings or somewhere + final boolean isSettingsEnabledFillDialog = true; + synchronized (mLock) { + return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled; + } + } + + private void setFillDialogDisabled() { + synchronized (mLock) { + mSessionFlags.mFillDialogDisabled = true; + } + notifyClientFillDialogTriggerIds(null); + } + + private void setFillDialogDisabledAndStartInput() { + if (getUiForShowing().isFillDialogShowing()) { + setFillDialogDisabled(); + final AutofillId id; + synchronized (mLock) { + id = mCurrentViewId; + } + requestShowSoftInput(id); + } + } + + private boolean requestShowFillDialog(FillResponse response, + AutofillId filledId, String filterText) { + if (!isFillDialogUiEnabled()) { + // Unsupported fill dialog UI + return false; + } + + final AutofillId[] ids = response.getFillDialogTriggerIds(); + if (ids == null || !ArrayUtils.contains(ids, filledId)) { + return false; + } + + final Drawable serviceIcon = getServiceIcon(); + + getUiForShowing().showFillDialog(filledId, response, filterText, + mService.getServicePackageName(), mComponentName, serviceIcon, this); + return true; + } + + @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's + // actually the same object as mLock. + // TODO: Expose mService.mLock or redesign instead. + private Drawable getServiceIcon() { + synchronized (mLock) { + return mService.getServiceIconLocked(); + } + } + /** * Returns whether we made a request to show inline suggestions. */ @@ -3412,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); } @@ -3584,9 +3816,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.getRemoteInlineSuggestionRenderServiceLocked(); if (remoteRenderService != null && (mSessionFlags.mAugmentedAutofillOnly - || !mSessionFlags.mInlineSupportedByService - || mSessionFlags.mExpiredResponse) - && isViewFocusedLocked(flags)) { + || !mSessionFlags.mInlineSupportedByService + || mSessionFlags.mExpiredResponse) + && isViewFocusedLocked(flags) + || isFillDialogUiEnabled()) { if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback( (extras) -> { @@ -3625,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 @@ -3642,6 +3880,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mClientState = newClientState != null ? newClientState : newResponse.getClientState(); setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false); + updateFillDialogTriggerIdsLocked(); updateTrackedIdsLocked(); if (mCurrentViewId == null) { @@ -4176,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/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java index adb1e3e43731..4a14f1420cf2 100644 --- a/services/autofill/java/com/android/server/autofill/ViewState.java +++ b/services/autofill/java/com/android/server/autofill/ViewState.java @@ -82,6 +82,8 @@ final class ViewState { public static final int STATE_INLINE_DISABLED = 0x8000; /** The View is waiting for an inline suggestions request from IME.*/ public static final int STATE_PENDING_CREATE_INLINE_REQUEST = 0x10000; + /** Fill dialog were shown for this View. */ + public static final int STATE_FILL_DIALOG_SHOWN = 0x20000; public final AutofillId id; diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index 71c3c16a2c06..056ab92fffb2 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -66,6 +66,7 @@ public final class AutoFillUI { private @Nullable FillUi mFillUi; private @Nullable SaveUi mSaveUi; + private @Nullable DialogFillUi mFillDialog; private @Nullable AutoFillUiCallback mCallback; @@ -90,6 +91,7 @@ public final class AutoFillUI { void startIntentSender(IntentSender intentSender, Intent intent); void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent); void cancelSession(); + void requestShowSoftInput(AutofillId id); } public AutoFillUI(@NonNull Context context) { @@ -155,6 +157,12 @@ public final class AutoFillUI { } /** + * Hides the fill UI. + */ + public void hideFillDialog(@NonNull AutoFillUiCallback callback) { + mHandler.post(() -> hideFillDialogUiThread(callback)); + } + /** * Filters the options in the fill UI. * * @param filterText The filter prefix. @@ -369,6 +377,62 @@ public final class AutoFillUI { } /** + * Shows the UI asking the user to choose for autofill. + */ + public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response, + @Nullable String filterText, @Nullable String servicePackageName, + @NonNull ComponentName componentName, @Nullable Drawable serviceIcon, + @NonNull AutoFillUiCallback callback) { + if (sVerbose) { + Slog.v(TAG, "showFillDialog for " + + componentName.toShortString() + ": " + response); + } + + // TODO: enable LogMaker + + mHandler.post(() -> { + if (callback != mCallback) { + return; + } + hideAllUiThread(callback); + mFillDialog = new DialogFillUi(mContext, response, focusedId, filterText, + serviceIcon, servicePackageName, componentName, mOverlayControl, + mUiModeMgr.isNightMode(), new DialogFillUi.UiCallback() { + @Override + public void onResponsePicked(FillResponse response) { + hideFillDialogUiThread(callback); + if (mCallback != null) { + mCallback.authenticate(response.getRequestId(), + AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED, + response.getAuthentication(), response.getClientState(), + /* authenticateInline= */ false); + } + } + + @Override + public void onDatasetPicked(Dataset dataset) { + hideFillDialogUiThread(callback); + if (mCallback != null) { + final int datasetIndex = response.getDatasets().indexOf(dataset); + mCallback.fill(response.getRequestId(), datasetIndex, dataset); + } + } + + @Override + public void onCanceled() { + hideFillDialogUiThread(callback); + callback.requestShowSoftInput(focusedId); + } + + @Override + public void startIntentSender(IntentSender intentSender) { + mCallback.startIntentSenderAndFinishSession(intentSender); + } + }); + }); + } + + /** * Executes an operation in the pending save UI, if any. */ public void onPendingSaveUi(int operation, @NonNull IBinder token) { @@ -400,6 +464,10 @@ public final class AutoFillUI { return mSaveUi == null ? false : mSaveUi.isShowing(); } + public boolean isFillDialogShowing() { + return mFillDialog == null ? false : mFillDialog.isShowing(); + } + public void dump(PrintWriter pw) { pw.println("Autofill UI"); final String prefix = " "; @@ -417,6 +485,12 @@ public final class AutoFillUI { } else { pw.print(prefix); pw.println("showsSaveUi: false"); } + if (mFillDialog != null) { + pw.print(prefix); pw.println("showsFillDialog: true"); + mFillDialog.dump(pw, prefix2); + } else { + pw.print(prefix); pw.println("showsFillDialog: false"); + } } @android.annotation.UiThread @@ -442,6 +516,14 @@ public final class AutoFillUI { } @android.annotation.UiThread + private void hideFillDialogUiThread(@Nullable AutoFillUiCallback callback) { + if (mFillDialog != null && (callback == null || callback == mCallback)) { + mFillDialog.destroy(); + mFillDialog = null; + } + } + + @android.annotation.UiThread private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi, boolean notifyClient) { if (mSaveUi == null) { // Calling destroySaveUiUiThread() twice is normal - it usually happens when the @@ -475,12 +557,14 @@ public final class AutoFillUI { private void destroyAllUiThread(@Nullable PendingUi pendingSaveUi, @Nullable AutoFillUiCallback callback, boolean notifyClient) { hideFillUiUiThread(callback, notifyClient); + hideFillDialogUiThread(callback); destroySaveUiUiThread(pendingSaveUi, notifyClient); } @android.annotation.UiThread private void hideAllUiThread(@Nullable AutoFillUiCallback callback) { hideFillUiUiThread(callback, true); + hideFillDialogUiThread(callback); final PendingUi pendingSaveUi = hideSaveUiUiThread(callback); if (pendingSaveUi != null && pendingSaveUi.getState() == PendingUi.STATE_FINISHED) { if (sDebug) { diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java new file mode 100644 index 000000000000..e1229939e2ca --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java @@ -0,0 +1,629 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.autofill.ui; + +import static com.android.server.autofill.Helper.sDebug; +import static com.android.server.autofill.Helper.sVerbose; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Dialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.IntentSender; +import android.graphics.drawable.Drawable; +import android.service.autofill.Dataset; +import android.service.autofill.FillResponse; +import android.text.TextUtils; +import android.util.PluralsMessageFormatter; +import android.util.Slog; +import android.view.ContextThemeWrapper; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.RemoteViews; +import android.widget.TextView; + +import com.android.internal.R; +import com.android.server.autofill.AutofillManagerService; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * A dialog to show Autofill suggestions. + * + * This fill dialog UI shows as a bottom sheet style dialog. This dialog UI + * provides a larger area to display the suggestions, it provides a more + * conspicuous and efficient interface to the user. So it is easy for users + * to pay attention to the datasets and selecting one of them. + */ +final class DialogFillUi { + + private static final String TAG = "DialogFillUi"; + private static final int THEME_ID_LIGHT = + R.style.Theme_DeviceDefault_Light_Autofill_Save; + private static final int THEME_ID_DARK = + R.style.Theme_DeviceDefault_Autofill_Save; + + interface UiCallback { + void onResponsePicked(@NonNull FillResponse response); + void onDatasetPicked(@NonNull Dataset dataset); + void onCanceled(); + void startIntentSender(IntentSender intentSender); + } + + private final @NonNull Dialog mDialog; + private final @NonNull OverlayControl mOverlayControl; + private final String mServicePackageName; + private final ComponentName mComponentName; + private final int mThemeId; + private final @NonNull Context mContext; + private final @NonNull UiCallback mCallback; + private final @NonNull ListView mListView; + private final @Nullable ItemsAdapter mAdapter; + private final int mVisibleDatasetsMaxCount; + + private @Nullable String mFilterText; + private @Nullable AnnounceFilterResult mAnnounceFilterResult; + private boolean mDestroyed; + + DialogFillUi(@NonNull Context context, @NonNull FillResponse response, + @NonNull AutofillId focusedViewId, @Nullable String filterText, + @Nullable Drawable serviceIcon, @Nullable String servicePackageName, + @Nullable ComponentName componentName, @NonNull OverlayControl overlayControl, + boolean nightMode, @NonNull UiCallback callback) { + if (sVerbose) Slog.v(TAG, "nightMode: " + nightMode); + mThemeId = nightMode ? THEME_ID_DARK : THEME_ID_LIGHT; + mCallback = callback; + mOverlayControl = overlayControl; + mServicePackageName = servicePackageName; + mComponentName = componentName; + + mContext = new ContextThemeWrapper(context, mThemeId); + final LayoutInflater inflater = LayoutInflater.from(mContext); + final View decor = inflater.inflate(R.layout.autofill_fill_dialog, null); + + setServiceIcon(decor, serviceIcon); + setHeader(decor, response); + + mVisibleDatasetsMaxCount = getVisibleDatasetsMaxCount(); + + if (response.getAuthentication() != null) { + mListView = null; + mAdapter = null; + try { + initialAuthenticationLayout(decor, response); + } catch (RuntimeException e) { + callback.onCanceled(); + Slog.e(TAG, "Error inflating remote views", e); + mDialog = null; + return; + } + } else { + final List<ViewItem> items = createDatasetItems(response, focusedViewId); + mAdapter = new ItemsAdapter(items); + mListView = decor.findViewById(R.id.autofill_dialog_list); + initialDatasetLayout(decor, filterText); + } + + setDismissButton(decor); + + mDialog = new Dialog(mContext, mThemeId); + mDialog.setContentView(decor); + setDialogParamsAsBottomSheet(); + + show(); + } + + private int getVisibleDatasetsMaxCount() { + if (AutofillManagerService.getVisibleDatasetsMaxCount() > 0) { + final int maxCount = AutofillManagerService.getVisibleDatasetsMaxCount(); + if (sVerbose) { + Slog.v(TAG, "overriding maximum visible datasets to " + maxCount); + } + return maxCount; + } else { + return mContext.getResources() + .getInteger(com.android.internal.R.integer.autofill_max_visible_datasets); + } + } + + private void setDialogParamsAsBottomSheet() { + final Window window = mDialog.getWindow(); + window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); + window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS); + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + window.setGravity(Gravity.BOTTOM | Gravity.CENTER); + window.setCloseOnTouchOutside(true); + final WindowManager.LayoutParams params = window.getAttributes(); + params.width = WindowManager.LayoutParams.MATCH_PARENT; + params.accessibilityTitle = + mContext.getString(R.string.autofill_picker_accessibility_title); + params.windowAnimations = R.style.AutofillSaveAnimation; + } + + private void setServiceIcon(View decor, Drawable serviceIcon) { + if (serviceIcon == null) { + return; + } + + final ImageView iconView = decor.findViewById(R.id.autofill_service_icon); + final int actualWidth = serviceIcon.getMinimumWidth(); + final int actualHeight = serviceIcon.getMinimumHeight(); + if (sDebug) { + Slog.d(TAG, "Adding service icon " + + "(" + actualWidth + "x" + actualHeight + ")"); + } + iconView.setImageDrawable(serviceIcon); + iconView.setVisibility(View.VISIBLE); + } + + private void setHeader(View decor, FillResponse response) { + final RemoteViews presentation = response.getDialogHeader(); + if (presentation == null) { + return; + } + + final ViewGroup container = decor.findViewById(R.id.autofill_dialog_header); + final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> { + if (pendingIntent != null) { + mCallback.startIntentSender(pendingIntent.getIntentSender()); + } + return true; + }; + + final View content = presentation.applyWithTheme( + mContext, (ViewGroup) decor, interceptionHandler, mThemeId); + container.addView(content); + container.setVisibility(View.VISIBLE); + } + + private void setDismissButton(View decor) { + final TextView noButton = decor.findViewById(R.id.autofill_dialog_no); + noButton.setOnClickListener((v) -> mCallback.onCanceled()); + } + + private void setContinueButton(View decor, View.OnClickListener listener) { + final TextView yesButton = decor.findViewById(R.id.autofill_dialog_yes); + // set "Continue" by default + yesButton.setText(R.string.autofill_continue_yes); + yesButton.setOnClickListener(listener); + } + + private void initialAuthenticationLayout(View decor, FillResponse response) { + RemoteViews presentation = response.getDialogPresentation(); + if (presentation == null) { + presentation = response.getPresentation(); + } + if (presentation == null) { + throw new RuntimeException("No presentation for fill dialog authentication"); + } + + // insert authentication item under autofill_dialog_container + final ViewGroup container = decor.findViewById(R.id.autofill_dialog_container); + final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> { + if (pendingIntent != null) { + mCallback.startIntentSender(pendingIntent.getIntentSender()); + } + return true; + }; + final View content = presentation.applyWithTheme( + mContext, (ViewGroup) decor, interceptionHandler, mThemeId); + container.addView(content); + container.setVisibility(View.VISIBLE); + container.setFocusable(true); + container.setOnClickListener(v -> mCallback.onResponsePicked(response)); + // just single item, set up continue button + setContinueButton(decor, v -> mCallback.onResponsePicked(response)); + } + + private ArrayList<ViewItem> createDatasetItems(FillResponse response, + AutofillId focusedViewId) { + final int datasetCount = response.getDatasets().size(); + if (sVerbose) { + Slog.v(TAG, "Number datasets: " + datasetCount + " max visible: " + + mVisibleDatasetsMaxCount); + } + + final RemoteViews.InteractionHandler interceptionHandler = (view, pendingIntent, r) -> { + if (pendingIntent != null) { + mCallback.startIntentSender(pendingIntent.getIntentSender()); + } + return true; + }; + + final ArrayList<ViewItem> items = new ArrayList<>(datasetCount); + for (int i = 0; i < datasetCount; i++) { + final Dataset dataset = response.getDatasets().get(i); + final int index = dataset.getFieldIds().indexOf(focusedViewId); + if (index >= 0) { + RemoteViews presentation = dataset.getFieldDialogPresentation(index); + if (presentation == null) { + Slog.w(TAG, "fallback to presentation"); + presentation = dataset.getFieldPresentation(index); + } + if (presentation == null) { + Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because " + + "service didn't provide a presentation for it on " + dataset); + continue; + } + final View view; + try { + if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId); + view = presentation.applyWithTheme( + mContext, null, interceptionHandler, mThemeId); + } catch (RuntimeException e) { + Slog.e(TAG, "Error inflating remote views", e); + continue; + } + // TODO: Extract the shared filtering logic here and in FillUi to a common + // method. + final Dataset.DatasetFieldFilter filter = dataset.getFilter(index); + Pattern filterPattern = null; + String valueText = null; + boolean filterable = true; + if (filter == null) { + final AutofillValue value = dataset.getFieldValues().get(index); + if (value != null && value.isText()) { + valueText = value.getTextValue().toString().toLowerCase(); + } + } else { + filterPattern = filter.pattern; + if (filterPattern == null) { + if (sVerbose) { + Slog.v(TAG, "Explicitly disabling filter at id " + focusedViewId + + " for dataset #" + index); + } + filterable = false; + } + } + + items.add(new ViewItem(dataset, filterPattern, filterable, valueText, view)); + } + } + return items; + } + + private void initialDatasetLayout(View decor, String filterText) { + final AdapterView.OnItemClickListener onItemClickListener = + (adapter, view, position, id) -> { + final ViewItem vi = mAdapter.getItem(position); + mCallback.onDatasetPicked(vi.dataset); + }; + + mListView.setAdapter(mAdapter); + mListView.setVisibility(View.VISIBLE); + mListView.setOnItemClickListener(onItemClickListener); + + if (mAdapter.getCount() == 1) { + // just single item, set up continue button + setContinueButton(decor, (v) -> + onItemClickListener.onItemClick(null, null, 0, 0)); + } + + if (filterText == null) { + mFilterText = null; + } else { + mFilterText = filterText.toLowerCase(); + } + + final int oldCount = mAdapter.getCount(); + mAdapter.getFilter().filter(mFilterText, (count) -> { + if (mDestroyed) { + return; + } + if (count <= 0) { + if (sDebug) { + final int size = mFilterText == null ? 0 : mFilterText.length(); + Slog.d(TAG, "No dataset matches filter with " + size + " chars"); + } + mCallback.onCanceled(); + } else { + + if (mAdapter.getCount() > mVisibleDatasetsMaxCount) { + mListView.setVerticalScrollBarEnabled(true); + mListView.onVisibilityAggregated(true); + } else { + mListView.setVerticalScrollBarEnabled(false); + } + if (mAdapter.getCount() != oldCount) { + mListView.requestLayout(); + } + } + }); + } + + private void show() { + Slog.i(TAG, "Showing fill dialog"); + mDialog.show(); + mOverlayControl.hideOverlays(); + } + + boolean isShowing() { + return mDialog.isShowing(); + } + + void hide() { + if (sVerbose) Slog.v(TAG, "Hiding fill dialog."); + try { + mDialog.hide(); + } finally { + mOverlayControl.showOverlays(); + } + } + + void destroy() { + try { + if (sDebug) Slog.d(TAG, "destroy()"); + throwIfDestroyed(); + + mDialog.dismiss(); + mDestroyed = true; + } finally { + mOverlayControl.showOverlays(); + } + } + + private void throwIfDestroyed() { + if (mDestroyed) { + throw new IllegalStateException("cannot interact with a destroyed instance"); + } + } + + @Override + public String toString() { + // TODO toString + return "NO TITLE"; + } + + void dump(PrintWriter pw, String prefix) { + + pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName); + pw.print(prefix); pw.print("app: "); pw.println(mComponentName.toShortString()); + pw.print(prefix); pw.print("theme id: "); pw.print(mThemeId); + switch (mThemeId) { + case THEME_ID_DARK: + pw.println(" (dark)"); + break; + case THEME_ID_LIGHT: + pw.println(" (light)"); + break; + default: + pw.println("(UNKNOWN_MODE)"); + break; + } + final View view = mDialog.getWindow().getDecorView(); + final int[] loc = view.getLocationOnScreen(); + pw.print(prefix); pw.print("coordinates: "); + pw.print('('); pw.print(loc[0]); pw.print(','); pw.print(loc[1]); pw.print(')'); + pw.print('('); + pw.print(loc[0] + view.getWidth()); pw.print(','); + pw.print(loc[1] + view.getHeight()); pw.println(')'); + pw.print(prefix); pw.print("destroyed: "); pw.println(mDestroyed); + } + + private void announceSearchResultIfNeeded() { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + if (mAnnounceFilterResult == null) { + mAnnounceFilterResult = new AnnounceFilterResult(); + } + mAnnounceFilterResult.post(); + } + } + + // TODO: Below code copied from FullUi, Extract the shared filtering logic here + // and in FillUi to a common method. + private final class AnnounceFilterResult implements Runnable { + private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec + + public void post() { + remove(); + mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY); + } + + public void remove() { + mListView.removeCallbacks(this); + } + + @Override + public void run() { + final int count = mListView.getAdapter().getCount(); + final String text; + if (count <= 0) { + text = mContext.getString(R.string.autofill_picker_no_suggestions); + } else { + Map<String, Object> arguments = new HashMap<>(); + arguments.put("count", count); + text = PluralsMessageFormatter.format(mContext.getResources(), + arguments, + R.string.autofill_picker_some_suggestions); + } + mListView.announceForAccessibility(text); + } + } + + private final class ItemsAdapter extends BaseAdapter implements Filterable { + private @NonNull final List<ViewItem> mAllItems; + + private @NonNull final List<ViewItem> mFilteredItems = new ArrayList<>(); + + ItemsAdapter(@NonNull List<ViewItem> items) { + mAllItems = Collections.unmodifiableList(new ArrayList<>(items)); + mFilteredItems.addAll(items); + } + + @Override + public Filter getFilter() { + return new Filter() { + @Override + protected FilterResults performFiltering(CharSequence filterText) { + // No locking needed as mAllItems is final an immutable + final List<ViewItem> filtered = mAllItems.stream() + .filter((item) -> item.matches(filterText)) + .collect(Collectors.toList()); + final FilterResults results = new FilterResults(); + results.values = filtered; + results.count = filtered.size(); + return results; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + final boolean resultCountChanged; + final int oldItemCount = mFilteredItems.size(); + mFilteredItems.clear(); + if (results.count > 0) { + @SuppressWarnings("unchecked") final List<ViewItem> items = + (List<ViewItem>) results.values; + mFilteredItems.addAll(items); + } + resultCountChanged = (oldItemCount != mFilteredItems.size()); + if (resultCountChanged) { + announceSearchResultIfNeeded(); + } + notifyDataSetChanged(); + } + }; + } + + @Override + public int getCount() { + return mFilteredItems.size(); + } + + @Override + public ViewItem getItem(int position) { + return mFilteredItems.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return getItem(position).view; + } + + @Override + public String toString() { + return "ItemsAdapter: [all=" + mAllItems + ", filtered=" + mFilteredItems + "]"; + } + } + + + /** + * An item for the list view - either a (clickable) dataset or a (read-only) header / footer. + */ + private static class ViewItem { + public final @Nullable String value; + public final @Nullable Dataset dataset; + public final @NonNull View view; + public final @Nullable Pattern filter; + public final boolean filterable; + + /** + * Default constructor. + * + * @param dataset dataset associated with the item + * @param filter optional filter set by the service to determine how the item should be + * filtered + * @param filterable optional flag set by the service to indicate this item should not be + * filtered (typically used when the dataset has value but it's sensitive, like a password) + * @param value dataset value + * @param view dataset presentation. + */ + ViewItem(@NonNull Dataset dataset, @Nullable Pattern filter, boolean filterable, + @Nullable String value, @NonNull View view) { + this.dataset = dataset; + this.value = value; + this.view = view; + this.filter = filter; + this.filterable = filterable; + } + + /** + * Returns whether this item matches the value input by the user so it can be included + * in the filtered datasets. + */ + public boolean matches(CharSequence filterText) { + if (TextUtils.isEmpty(filterText)) { + // Always show item when the user input is empty + return true; + } + if (!filterable) { + // Service explicitly disabled filtering using a null Pattern. + return false; + } + final String constraintLowerCase = filterText.toString().toLowerCase(); + if (filter != null) { + // Uses pattern provided by service + return filter.matcher(constraintLowerCase).matches(); + } else { + // Compares it with dataset value with dataset + return (value == null) + ? (dataset.getAuthentication() == null) + : value.toLowerCase().startsWith(constraintLowerCase); + } + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder("ViewItem:[view=") + .append(view.getAutofillId()); + final String datasetId = dataset == null ? null : dataset.getId(); + if (datasetId != null) { + builder.append(", dataset=").append(datasetId); + } + if (value != null) { + // Cannot print value because it could contain PII + builder.append(", value=").append(value.length()).append("_chars"); + } + if (filterable) { + builder.append(", filterable"); + } + if (filter != null) { + // Filter should not have PII, but it could be a huge regexp + builder.append(", filter=").append(filter.pattern().length()).append("_chars"); + } + return builder.append(']').toString(); + } + } +} 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/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/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index 16645dfd386d..06c11fa4a20c 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -673,6 +673,12 @@ public class PersistentDataBlockService extends SystemService { throw new UnsupportedOperationException("cannot read frp credential"); } } + + @Override + public String getPersistentDataPackageName() { + enforcePersistentDataBlockAccess(); + return mContext.getString(R.string.config_persistentDataPackageName); + } }; private PersistentDataBlockManagerInternal mInternalService = diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index f71f02a6ec4e..8aeae6ae49b9 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -123,6 +123,7 @@ import android.provider.DocumentsContract; import android.provider.Downloads; import android.provider.MediaStore; import android.provider.Settings; +import android.service.storage.ExternalStorageService; import android.sysprop.VoldProperties; import android.text.TextUtils; import android.text.format.DateUtils; @@ -491,6 +492,8 @@ class StorageManagerService extends IStorageManager.Stub @GuardedBy("mAppFuseLock") private AppFuseBridge mAppFuseBridge = null; + private HashMap<Integer, Integer> mUserSharesMediaWith = new HashMap<>(); + /** Matches known application dir paths. The first group contains the generic part of the path, * the second group contains the user id (or null if it's a public volume without users), the * third group contains the package name, and the fourth group the remainder of the path. @@ -1235,6 +1238,21 @@ class StorageManagerService extends IStorageManager.Stub private void onUnlockUser(int userId) { Slog.d(TAG, "onUnlockUser " + userId); + if (userId != UserHandle.USER_SYSTEM) { + // Check if this user shares media with another user + try { + Context userContext = mContext.createPackageContextAsUser("system", 0, + UserHandle.of(userId)); + UserManager um = userContext.getSystemService(UserManager.class); + if (um != null && um.isMediaSharedWithParent()) { + int parentUserId = um.getProfileParent(userId).id; + mUserSharesMediaWith.put(userId, parentUserId); + mUserSharesMediaWith.put(parentUserId, userId); + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to create user context for user " + userId); + } + } // We purposefully block here to make sure that user-specific // staging area is ready so it's ready for zygote-forked apps to // bind mount against. @@ -3971,6 +3989,29 @@ class StorageManagerService extends IStorageManager.Stub final boolean realState = (flags & StorageManager.FLAG_REAL_STATE) != 0; final boolean includeInvisible = (flags & StorageManager.FLAG_INCLUDE_INVISIBLE) != 0; final boolean includeRecent = (flags & StorageManager.FLAG_INCLUDE_RECENT) != 0; + final boolean includeSharedProfile = + (flags & StorageManager.FLAG_INCLUDE_SHARED_PROFILE) != 0; + + // Only Apps with MANAGE_EXTERNAL_STORAGE should call the API with includeSharedProfile + if (includeSharedProfile) { + try { + // Get package name for calling app and + // verify it has MANAGE_EXTERNAL_STORAGE permission + final String[] packagesFromUid = mIPackageManager.getPackagesForUid(callingUid); + if (packagesFromUid == null) { + throw new SecurityException("Unknown uid " + callingUid); + } + // Checking first entry in packagesFromUid is enough as using "sharedUserId" + // mechanism is rare and discouraged. Also, Apps that share same UID share the same + // permissions. + if (!mStorageManagerInternal.hasExternalStorageAccess(callingUid, + packagesFromUid[0])) { + throw new SecurityException("Only File Manager Apps permitted"); + } + } catch (RemoteException re) { + throw new SecurityException("Unknown uid " + callingUid, re); + } + } // Report all volumes as unmounted until we've recorded that user 0 has unlocked. There // are no guarantees that callers will see a consistent view of the volume before that @@ -4002,6 +4043,7 @@ class StorageManagerService extends IStorageManager.Stub final ArrayList<StorageVolume> res = new ArrayList<>(); final ArraySet<String> resUuids = new ArraySet<>(); + final int userIdSharingMedia = mUserSharesMediaWith.getOrDefault(userId, -1); synchronized (mLock) { for (int i = 0; i < mVolumes.size(); i++) { final String volId = mVolumes.keyAt(i); @@ -4014,6 +4056,11 @@ class StorageManagerService extends IStorageManager.Stub if (vol.getMountUserId() == userId) { break; } + if (includeSharedProfile && vol.getMountUserId() == userIdSharingMedia) { + // If the volume belongs to a user we share media with, + // return it too. + break; + } // Skip if emulated volume not for userId default: continue; @@ -4021,10 +4068,12 @@ class StorageManagerService extends IStorageManager.Stub boolean match = false; if (forWrite) { - match = vol.isVisibleForWrite(userId); + match = vol.isVisibleForWrite(userId) + || (includeSharedProfile && vol.isVisibleForWrite(userIdSharingMedia)); } else { match = vol.isVisibleForUser(userId) - || (includeInvisible && vol.getPath() != null); + || (includeInvisible && vol.getPath() != null) + || (includeSharedProfile && vol.isVisibleForRead(userIdSharingMedia)); } if (!match) continue; @@ -4045,9 +4094,13 @@ class StorageManagerService extends IStorageManager.Stub reportUnmounted = true; } - final StorageVolume userVol = vol.buildStorageVolume(mContext, userId, + int volUserId = userId; + if (volUserId != vol.getMountUserId() && vol.getMountUserId() >= 0) { + volUserId = vol.getMountUserId(); + } + final StorageVolume userVol = vol.buildStorageVolume(mContext, volUserId, reportUnmounted); - if (vol.isPrimary()) { + if (vol.isPrimary() && vol.getMountUserId() == userId) { res.add(0, userVol); foundPrimary = true; } else { diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index 8a21a0fb2e0e..14d73f6c8592 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -145,7 +145,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> /** * The uid battery usage stats data from our last query, it does not include snapshot data. */ - // No lock is needed. + @GuardedBy("mLock") private final SparseDoubleArray mLastUidBatteryUsage = new SparseDoubleArray(); // No lock is needed. @@ -155,12 +155,15 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> private final SparseDoubleArray mTmpUidBatteryUsage2 = new SparseDoubleArray(); // No lock is needed. + private final SparseDoubleArray mTmpUidBatteryUsageInWindow = new SparseDoubleArray(); + + // No lock is needed. private final ArraySet<UserHandle> mTmpUserIds = new ArraySet<>(); /** * The start timestamp of the battery usage stats result from our last query. */ - // No lock is needed. + @GuardedBy("mLock") private long mLastUidBatteryUsageStartTs; // For debug only. @@ -296,8 +299,10 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> checkBatteryUsageStats(); } else { // We didn't do the battery stats update above, schedule a check later. - scheduleBatteryUsageStatsUpdateIfNecessary( - mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs - now); + synchronized (mLock) { + scheduleBatteryUsageStatsUpdateIfNecessary( + mLastBatteryUsageSamplingTs + mBatteryUsageStatsPollingMinIntervalMs - now); + } } } @@ -305,7 +310,10 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> final long now = SystemClock.elapsedRealtime(); final AppBatteryPolicy bgPolicy = mInjector.getPolicy(); try { - final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow; + final SparseDoubleArray uidConsumers = mTmpUidBatteryUsageInWindow; + synchronized (mLock) { + copyUidBatteryUsage(mUidBatteryUsageInWindow, uidConsumers); + } final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs); for (int i = 0, size = uidConsumers.size(); i < size; i++) { final int uid = uidConsumers.keyAt(i); @@ -408,7 +416,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> if (curDuration >= windowSize) { // If we do have long enough data for the window, save it. - copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration); + synchronized (mLock) { + copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration); + } needUpdateUidBatteryUsageInWindow = false; } @@ -416,8 +426,11 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> mTmpUidBatteryUsage2.clear(); copyUidBatteryUsage(buf, mTmpUidBatteryUsage2); - final long lastUidBatteryUsageStartTs = mLastUidBatteryUsageStartTs; - mLastUidBatteryUsageStartTs = curStart; + final long lastUidBatteryUsageStartTs; + synchronized (mLock) { + lastUidBatteryUsageStartTs = mLastUidBatteryUsageStartTs; + mLastUidBatteryUsageStartTs = curStart; + } if (curStart > lastUidBatteryUsageStartTs && lastUidBatteryUsageStartTs > 0) { // The battery usage stats committed data since our last query, // let's query the snapshots to get the data since last start. @@ -429,42 +442,47 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> } if (needUpdateUidBatteryUsageInWindow && curDuration > windowSize) { // If we do have long enough data for the window, save it. - copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration); + synchronized (mLock) { + copyUidBatteryUsage(buf, mUidBatteryUsageInWindow, windowSize * 1.0d / curDuration); + } needUpdateUidBatteryUsageInWindow = false; } // Add the delta into the global records. - for (int i = 0, size = buf.size(); i < size; i++) { - final int uid = buf.keyAt(i); - final int index = mUidBatteryUsage.indexOfKey(uid); - final double delta = Math.max(0.0d, - buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d)); - final double before; - if (index >= 0) { - before = mUidBatteryUsage.valueAt(index); - mUidBatteryUsage.setValueAt(index, before + delta); - } else { - before = 0.0d; - mUidBatteryUsage.put(uid, delta); - } - if (DEBUG_BACKGROUND_BATTERY_TRACKER) { - final double actualDelta = buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d); - String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before - + ", after=" + mUidBatteryUsage.get(uid, 0.0d) + ", delta=" + actualDelta - + ", last=" + mLastUidBatteryUsage.get(uid, 0.0d) - + ", curStart=" + curStart - + ", lastLastStart=" + lastUidBatteryUsageStartTs - + ", thisLastStart=" + mLastUidBatteryUsageStartTs; - if (actualDelta < 0.0d) { - // Something is wrong, the battery usage shouldn't be negative. - Slog.e(TAG, msg); + synchronized (mLock) { + for (int i = 0, size = buf.size(); i < size; i++) { + final int uid = buf.keyAt(i); + final int index = mUidBatteryUsage.indexOfKey(uid); + final double delta = Math.max(0.0d, + buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d)); + final double before; + if (index >= 0) { + before = mUidBatteryUsage.valueAt(index); + mUidBatteryUsage.setValueAt(index, before + delta); } else { - Slog.i(TAG, msg); + before = 0.0d; + mUidBatteryUsage.put(uid, delta); + } + if (DEBUG_BACKGROUND_BATTERY_TRACKER) { + final double actualDelta = buf.valueAt(i) - mLastUidBatteryUsage.get(uid, 0.0d); + String msg = "Updating mUidBatteryUsage uid=" + uid + ", before=" + before + + ", after=" + mUidBatteryUsage.get(uid, 0.0d) + + ", delta=" + actualDelta + + ", last=" + mLastUidBatteryUsage.get(uid, 0.0d) + + ", curStart=" + curStart + + ", lastLastStart=" + lastUidBatteryUsageStartTs + + ", thisLastStart=" + mLastUidBatteryUsageStartTs; + if (actualDelta < 0.0d) { + // Something is wrong, the battery usage shouldn't be negative. + Slog.e(TAG, msg); + } else { + Slog.i(TAG, msg); + } } } + // Now update the mLastUidBatteryUsage with the data we just saved above. + copyUidBatteryUsage(mTmpUidBatteryUsage2, mLastUidBatteryUsage); } - // Now update the mLastUidBatteryUsage with the data we just saved above. - copyUidBatteryUsage(mTmpUidBatteryUsage2, mLastUidBatteryUsage); mTmpUidBatteryUsage2.clear(); if (needUpdateUidBatteryUsageInWindow) { @@ -473,7 +491,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> .includeProcessStateData() .aggregateSnapshots(now - windowSize, lastUidBatteryUsageStartTs); updateBatteryUsageStatsOnceInternal(buf, builder, userIds, batteryStatsInternal); - copyUidBatteryUsage(buf, mUidBatteryUsageInWindow); + synchronized (mLock) { + copyUidBatteryUsage(buf, mUidBatteryUsageInWindow); + } } } @@ -584,38 +604,40 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> pw.print(prefix); pw.println("APP BATTERY STATE TRACKER:"); updateBatteryUsageStatsIfNecessary(mInjector.currentTimeMillis(), true); - final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow; - pw.print(" " + prefix); - pw.print("Boot="); - TimeUtils.dumpTime(pw, mBootTimestamp); - pw.print(" Last battery usage start="); - TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs); - pw.println(); - pw.print(" " + prefix); - pw.print("Battery usage over last "); - final String newPrefix = " " + prefix; - final AppBatteryPolicy bgPolicy = mInjector.getPolicy(); - final long now = SystemClock.elapsedRealtime(); - final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs); - pw.println(TimeUtils.formatDuration(now - since)); - if (uidConsumers.size() == 0) { - pw.print(newPrefix); - pw.println("(none)"); - } else { - for (int i = 0, size = uidConsumers.size(); i < size; i++) { - final int uid = uidConsumers.keyAt(i); - final double bgUsage = uidConsumers.valueAt(i); - final double exemptedUsage = mAppRestrictionController - .getUidBatteryExemptedUsageSince(uid, since, now); - final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage); - pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | " - + "%.3f mAh (%4.2f%%) | %.3f mAh\n", - newPrefix, UserHandle.formatUid(uid), - PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)), - bgUsage , bgPolicy.getPercentage(uid, bgUsage), - exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage), - reportedUsage, bgPolicy.getPercentage(-1, reportedUsage), - mUidBatteryUsage.get(uid, 0.0d)); + synchronized (mLock) { + final SparseDoubleArray uidConsumers = mUidBatteryUsageInWindow; + pw.print(" " + prefix); + pw.print("Boot="); + TimeUtils.dumpTime(pw, mBootTimestamp); + pw.print(" Last battery usage start="); + TimeUtils.dumpTime(pw, mLastUidBatteryUsageStartTs); + pw.println(); + pw.print(" " + prefix); + pw.print("Battery usage over last "); + final String newPrefix = " " + prefix; + final AppBatteryPolicy bgPolicy = mInjector.getPolicy(); + final long now = SystemClock.elapsedRealtime(); + final long since = Math.max(0, now - bgPolicy.mBgCurrentDrainWindowMs); + pw.println(TimeUtils.formatDuration(now - since)); + if (uidConsumers.size() == 0) { + pw.print(newPrefix); + pw.println("(none)"); + } else { + for (int i = 0, size = uidConsumers.size(); i < size; i++) { + final int uid = uidConsumers.keyAt(i); + final double bgUsage = uidConsumers.valueAt(i); + final double exemptedUsage = mAppRestrictionController + .getUidBatteryExemptedUsageSince(uid, since, now); + final double reportedUsage = Math.max(0.0d, bgUsage - exemptedUsage); + pw.format("%s%s: [%s] %.3f mAh (%4.2f%%) | %.3f mAh (%4.2f%%) | " + + "%.3f mAh (%4.2f%%) | %.3f mAh\n", + newPrefix, UserHandle.formatUid(uid), + PowerExemptionManager.reasonCodeToString(bgPolicy.shouldExemptUid(uid)), + bgUsage , bgPolicy.getPercentage(uid, bgUsage), + exemptedUsage, bgPolicy.getPercentage(-1, exemptedUsage), + reportedUsage, bgPolicy.getPercentage(-1, reportedUsage), + mUidBatteryUsage.get(uid, 0.0d)); + } } } super.dump(pw, prefix); diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index bd63a24e4e41..1315293abaa4 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -1314,7 +1314,7 @@ public final class AppRestrictionController { void onSystemReady() { mContext.registerReceiverForAllUsers(mActionButtonReceiver, new IntentFilter(ACTION_FGS_MANAGER_TRAMPOLINE), - MANAGE_ACTIVITY_TASKS, mBgController.mBgHandler); + MANAGE_ACTIVITY_TASKS, mBgController.mBgHandler, Context.RECEIVER_NOT_EXPORTED); } void postRequestBgRestrictedIfNecessary(String packageName, int uid) { 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/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/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index f1429a567564..3c9d29d77bbe 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -85,6 +85,7 @@ import com.android.server.SystemService; import com.android.server.SystemService.TargetUser; import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.List; /** @@ -159,6 +160,29 @@ public final class GameManagerService extends IGameManagerService.Stub { new GameManagerShellCommand().exec(this, in, out, err, args, callback, result); } + @Override + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + writer.println("Permission Denial: can't dump GameManagerService from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " without permission " + android.Manifest.permission.DUMP); + return; + } + if (args == null || args.length == 0) { + writer.println("*Dump GameManagerService*"); + dumpAllGameConfigs(writer); + } + } + + private void dumpAllGameConfigs(PrintWriter pw) { + final int userId = ActivityManager.getCurrentUser(); + String[] packageList = getInstalledGamePackageNames(userId); + for (final String packageName : packageList) { + pw.println(getInterventionList(packageName)); + } + } + class SettingsHandler extends Handler { SettingsHandler(Looper looper) { @@ -1266,8 +1290,7 @@ public final class GameManagerService extends IGameManagerService.Stub { .append(packageName); return listStrSb.toString(); } - listStrSb.append("\nPackage name: ") - .append(packageName) + listStrSb.append("\n") .append(packageConfig.toString()); return listStrSb.toString(); } diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index 9d4d1c1b0ff3..366718c65d84 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -38,6 +38,7 @@ import android.app.StatsManager.StatsPullAtomCallback; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; +import android.apphibernation.HibernationStats; import android.apphibernation.IAppHibernationService; import android.content.BroadcastReceiver; import android.content.Context; @@ -221,7 +222,7 @@ public final class AppHibernationService extends SystemService { } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, - "Caller does not have MANAGE_APP_HIBERNATION permission."); + "Caller did not have permission while calling " + methodName); userId = handleIncomingUser(userId, methodName); synchronized (mLock) { if (!checkUserStatesExist(userId, methodName)) { @@ -380,6 +381,46 @@ public final class AppHibernationService extends SystemService { } /** + * Return the stats from app hibernation for each package provided. + * + * @param packageNames the set of packages to return stats for. Returns all if null + * @return map from package to stats for that package + */ + public Map<String, HibernationStats> getHibernationStatsForUser( + @Nullable Set<String> packageNames, int userId) { + Map<String, HibernationStats> statsMap = new ArrayMap<>(); + String methodName = "getHibernationStatsForUser"; + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_APP_HIBERNATION, + "Caller does not have MANAGE_APP_HIBERNATION permission."); + userId = handleIncomingUser(userId, methodName); + synchronized (mLock) { + if (!checkUserStatesExist(userId, methodName)) { + return statsMap; + } + final Map<String, UserLevelState> userPackageStates = mUserStates.get(userId); + Set<String> pkgs = packageNames != null ? packageNames : userPackageStates.keySet(); + for (String pkgName : pkgs) { + if (!mPackageManagerInternal.canQueryPackage(Binder.getCallingUid(), pkgName)) { + // Package not visible to caller + continue; + } + if (!mGlobalHibernationStates.containsKey(pkgName) + || !userPackageStates.containsKey(pkgName)) { + Slog.w(TAG, String.format( + "No hibernation state associated with package %s user %d. Maybe" + + "the package was uninstalled? ", pkgName, userId)); + continue; + } + HibernationStats stats = new HibernationStats( + mGlobalHibernationStates.get(pkgName).savedByte); + statsMap.put(pkgName, stats); + } + } + return statsMap; + } + + /** * Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do * not hold {@link #mLock} while calling this to avoid deadlock scenarios. */ @@ -788,6 +829,13 @@ public final class AppHibernationService extends SystemService { } @Override + public Map<String, HibernationStats> getHibernationStatsForUser( + @Nullable List<String> packageNames, int userId) { + Set<String> pkgsSet = packageNames != null ? new ArraySet<>(packageNames) : null; + return mService.getHibernationStatsForUser(pkgsSet, userId); + } + + @Override public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) { 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/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java new file mode 100644 index 000000000000..8d28298542dc --- /dev/null +++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.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. */ + static BiometricContext getInstance() { + return BiometricContextProvider.sInstance.get(); + } + + /** 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..65e9e3dec60f --- /dev/null +++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.IBiometricContextListener; +import android.hardware.biometrics.common.OperationContext; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Singleton; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +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"; + + static final Singleton<BiometricContextProvider> sInstance = + new Singleton<BiometricContextProvider>() { + @Override + protected BiometricContextProvider create() { + return new BiometricContextProvider(IStatusBarService.Stub.asInterface( + ServiceManager.getService( + Context.STATUS_BAR_SERVICE)), null /* handler */); + } + }; + + @NonNull + private final Map<OperationContext, Consumer<OperationContext>> mSubscribers = + new ConcurrentHashMap<>(); + + @VisibleForTesting + BiometricContextProvider(@NonNull IStatusBarService service, @Nullable Handler handler) { + 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(); + } + } + }); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to register biometric context listener", e); + } + } + + private boolean mIsDozing = false; + + @Override + public boolean isAoD() { + return mIsDozing; + } + + @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 = mIsDozing; + consumer.accept(context); + }); + } +} 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..018839079e22 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java @@ -79,6 +79,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 +109,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; 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..e07a68c6d94d 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; } 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..949edd0e79ed 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; 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..eabafceab590 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 + protected 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,12 @@ 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 + 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/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..75a1e0cf21a8 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,7 +25,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.common.ICancellationSignal; import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.common.OperationReason; @@ -37,7 +36,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 +78,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( @@ -139,10 +158,10 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession> if (session.hasContextMethods()) { final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD + // TODO: add reason, id context.id = 0; context.reason = OperationReason.UNKNOWN; - context.isAoD = false; + context.isAoD = getBiometricContext().isAoD(); context.isCrypto = isCryptoOperation(); return session.getSession().authenticateWithContext(mOperationId, context); } else { 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..c79e60124c45 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,7 +21,6 @@ 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; @@ -29,7 +28,10 @@ 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 +54,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 @@ -102,10 +117,10 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements if (session.hasContextMethods()) { final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD + // TODO: add reason, id context.id = 0; context.reason = OperationReason.UNKNOWN; - context.isAoD = false; + context.isAoD = getBiometricContext().isAoD(); context.isCrypto = isCryptoOperation(); return session.getSession().detectInteractionWithContext(context); } else { 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..6f6dadc8a041 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,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.common.ICancellationSignal; import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.common.OperationReason; @@ -40,6 +39,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 +90,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); @@ -200,10 +201,10 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> { if (session.hasContextMethods()) { final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD + // TODO: add reason, id context.id = 0; context.reason = OperationReason.UNKNOWN; - context.isAoD = false; + context.isAoD = getBiometricContext().isAoD(); context.isCrypto = isCryptoOperation(); return session.getSession().enrollWithContext( hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle, context); 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..64b0892e92bb 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; @@ -237,6 +240,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), + BiometricContext.getInstance(), mSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client); @@ -247,6 +253,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mHandler.post(() -> { final InvalidationRequesterClient<Face> client = new InvalidationRequesterClient<>(mContext, userId, sensorId, + BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(), FaceUtils.getInstance(sensorId)); scheduleForSensor(sensorId, client); }); @@ -285,6 +292,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), + BiometricContext.getInstance(), mSensors.get(sensorId).getAuthenticatorIds(), callback); scheduleForSensor(sensorId, client); }); @@ -311,7 +321,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), + BiometricContext.getInstance()); scheduleForSensor(sensorId, client); }); } @@ -322,7 +335,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), + BiometricContext.getInstance(), challenge); scheduleForSensor(sensorId, client); }); } @@ -340,8 +355,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), + BiometricContext.getInstance(), maxTemplatesPerUser, debugConsent); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, @@ -372,8 +389,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), + BiometricContext.getInstance(), isStrongBiometric); scheduleForSensor(sensorId, client); }); @@ -396,7 +414,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), + BiometricContext.getInstance(), isStrongBiometric, mUsageStats, mSensors.get(sensorId).getLockoutCache(), allowBackgroundAuthentication, isKeyguardBypassEnabled); scheduleForSensor(sensorId, client); @@ -450,6 +470,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), + BiometricContext.getInstance(), mSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client); }); @@ -460,7 +483,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), + BiometricContext.getInstance(), hardwareAuthToken, mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); scheduleForSensor(sensorId, client); @@ -481,7 +507,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), BiometricContext.getInstance(), + feature, enabled, hardwareAuthToken); scheduleForSensor(sensorId, client); }); } @@ -498,7 +526,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), + BiometricContext.getInstance()); scheduleForSensor(sensorId, client); }); } @@ -518,13 +547,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), + BiometricContext.getInstance(), 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..fa07d120eb71 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. */ @@ -487,7 +491,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.getInstance(), + () -> mCurrentSession = null); } @NonNull @@ -523,6 +529,7 @@ public class Sensor { return new FaceStartUserClient(mContext, provider::getHalInstance, mToken, newUserId, mSensorProperties.sensorId, + BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(), 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..be1ed7d92aeb 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; @@ -533,7 +535,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), + BiometricContext.getInstance(), sSystemClock.millis()); mGeneratedChallengeCache = client; mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override @@ -562,7 +567,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), + BiometricContext.getInstance()); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, @@ -590,7 +598,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), + BiometricContext.getInstance()); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override @@ -637,7 +648,8 @@ 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, + createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient), + BiometricContext.getInstance(), isStrongBiometric, mLockoutTracker, mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled); mScheduler.scheduleClientMonitor(client); }); @@ -670,7 +682,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), + BiometricContext.getInstance(), mAuthenticatorIds); mScheduler.scheduleClientMonitor(client); }); } @@ -685,7 +700,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), + BiometricContext.getInstance(), mAuthenticatorIds); mScheduler.scheduleClientMonitor(client); }); } @@ -702,7 +720,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), + BiometricContext.getInstance(), hardwareAuthToken); mScheduler.scheduleClientMonitor(client); }); } @@ -723,7 +743,8 @@ 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), + BiometricContext.getInstance(), feature, enabled, hardwareAuthToken, faceId); mScheduler.scheduleClientMonitor(client); }); } @@ -742,7 +763,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), BiometricContext.getInstance(), + feature, faceId); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished( @@ -767,7 +790,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), + BiometricContext.getInstance(), enrolledList, FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, callback); }); @@ -890,7 +916,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), + BiometricContext.getInstance(), hasEnrolled, mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, @@ -904,6 +932,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/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..184e1ea7d003 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,9 +23,7 @@ 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; @@ -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); @@ -175,13 +178,12 @@ 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); + // TODO: add reason, id + mOperationContext.id = 0; + mOperationContext.reason = OperationReason.UNKNOWN; + mOperationContext.isAoD = getBiometricContext().isAoD(); + mOperationContext.isCrypto = isCryptoOperation(); + return session.getSession().authenticateWithContext(mOperationId, mOperationContext); } else { return session.getSession().authenticate(mOperationId); } 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..9d348e118b46 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,7 +20,6 @@ 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; @@ -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.ClientMonitorCallback; import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; @@ -54,11 +55,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*/); @@ -100,10 +100,10 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements if (session.hasContextMethods()) { final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD + // TODO: add reason, id context.id = 0; context.reason = OperationReason.UNKNOWN; - context.isAoD = false; + context.isAoD = getBiometricContext().isAoD(); context.isCrypto = isCryptoOperation(); return session.getSession().detectInteractionWithContext(context); } else { 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..ed16a6dd3b5c 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,7 +22,6 @@ 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; @@ -38,6 +37,8 @@ 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.sensors.BiometricNotificationUtils; import com.android.server.biometrics.sensors.BiometricUtils; import com.android.server.biometrics.sensors.ClientMonitorCallback; @@ -68,14 +69,15 @@ 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); @@ -177,10 +179,10 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps if (session.hasContextMethods()) { final OperationContext context = new OperationContext(); - // TODO: add reason, id, and isAoD + // TODO: add reason, id context.id = 0; context.reason = OperationReason.UNKNOWN; - context.isAoD = false; + context.isAoD = getBiometricContext().isAoD(); context.isCrypto = isCryptoOperation(); return session.getSession().enrollWithContext(hat, context); } else { 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..221ff94141e2 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; @@ -298,6 +301,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), + BiometricContext.getInstance(), mSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client); }); @@ -307,6 +313,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mHandler.post(() -> { final InvalidationRequesterClient<Fingerprint> client = new InvalidationRequesterClient<>(mContext, userId, sensorId, + BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(), FingerprintUtils.getInstance(sensorId)); scheduleForSensor(sensorId, client); }); @@ -317,7 +324,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), + BiometricContext.getInstance(), hardwareAuthToken, mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher); scheduleForSensor(sensorId, client); }); @@ -331,7 +341,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), + BiometricContext.getInstance()); scheduleForSensor(sensorId, client); }); } @@ -343,7 +355,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), + BiometricContext.getInstance(), challenge); scheduleForSensor(sensorId, client); }); } @@ -361,6 +376,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), + BiometricContext.getInstance(), mSensors.get(sensorId).getSensorProperties(), mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason); scheduleForSensor(sensorId, client, new ClientMonitorCallback() { @@ -399,8 +417,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), + BiometricContext.getInstance(), + mUdfpsOverlayController, isStrongBiometric); scheduleForSensor(sensorId, client, mFingerprintStateCallback); }); @@ -417,7 +437,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), + BiometricContext.getInstance(), isStrongBiometric, mTaskStackListener, mSensors.get(sensorId).getLockoutCache(), mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mSensors.get(sensorId).getSensorProperties()); @@ -479,6 +501,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), + BiometricContext.getInstance(), mSensors.get(sensorId).getAuthenticatorIds()); scheduleForSensor(sensorId, client, mFingerprintStateCallback); }); @@ -492,14 +517,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), + BiometricContext.getInstance(), + 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 +557,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), + BiometricContext.getInstance(), 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..27226b30f548 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. @@ -442,7 +446,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.getInstance(), + () -> mCurrentSession = null); } @NonNull @@ -478,6 +484,7 @@ class Sensor { return new FingerprintStartUserClient(mContext, provider::getHalInstance, mToken, newUserId, mSensorProperties.sensorId, + BiometricLogger.ofUnknown(mContext), BiometricContext.getInstance(), 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..6c35c8c91448 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; @@ -493,6 +495,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), + BiometricContext.getInstance(), this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override @@ -536,7 +541,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), + BiometricContext.getInstance(), mLockoutTracker); mScheduler.scheduleClientMonitor(client); }); } @@ -548,7 +556,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), + BiometricContext.getInstance()); mScheduler.scheduleClientMonitor(client); }); } @@ -559,7 +570,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), + BiometricContext.getInstance()); mScheduler.scheduleClientMonitor(client); }); } @@ -577,7 +591,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), + BiometricContext.getInstance(), + mUdfpsOverlayController, mSidefpsController, enrollReason); mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { @Override @@ -616,8 +634,9 @@ 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), + BiometricContext.getInstance(), mUdfpsOverlayController, isStrongBiometric); mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback); }); @@ -636,7 +655,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), + BiometricContext.getInstance(), isStrongBiometric, mTaskStackListener, mLockoutTracker, mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication, mSensorProperties); @@ -678,7 +699,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), + BiometricContext.getInstance(), mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback); }); } @@ -695,7 +719,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), + BiometricContext.getInstance(), mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback); }); } @@ -709,7 +736,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), + BiometricContext.getInstance(), enrolledList, FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); mScheduler.scheduleClientMonitor(client, callback); }); @@ -722,6 +752,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/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..f10d4e409b0e 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; 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/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java new file mode 100644 index 000000000000..767b2d18a69a --- /dev/null +++ b/services/core/java/com/android/server/display/BrightnessThrottler.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.display; + +import android.content.Context; +import android.hardware.display.BrightnessInfo; +import android.os.Handler; +import android.os.IThermalEventListener; +import android.os.IThermalService; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.Temperature; +import android.util.Slog; + +import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData; + +import java.io.PrintWriter; + +/** + * This class monitors various conditions, such as skin temperature throttling status, and limits + * the allowed brightness range accordingly. + */ +class BrightnessThrottler { + private static final String TAG = "BrightnessThrottler"; + private static final boolean DEBUG = false; + + private static final int THROTTLING_INVALID = -1; + + private final Injector mInjector; + private final Handler mHandler; + private BrightnessThrottlingData mThrottlingData; + private final Runnable mThrottlingChangeCallback; + private final SkinThermalStatusObserver mSkinThermalStatusObserver; + private int mThrottlingStatus; + private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; + private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason = + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + + BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData, + Runnable throttlingChangeCallback) { + this(new Injector(), handler, throttlingData, throttlingChangeCallback); + } + + BrightnessThrottler(Injector injector, Handler handler, BrightnessThrottlingData throttlingData, + Runnable throttlingChangeCallback) { + mInjector = injector; + mHandler = handler; + mThrottlingData = throttlingData; + mThrottlingChangeCallback = throttlingChangeCallback; + mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); + + resetThrottlingData(mThrottlingData); + } + + boolean deviceSupportsThrottling() { + return mThrottlingData != null; + } + + float getBrightnessCap() { + return mBrightnessCap; + } + + int getBrightnessMaxReason() { + return mBrightnessMaxReason; + } + + boolean isThrottled() { + return mBrightnessMaxReason != BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + } + + void stop() { + mSkinThermalStatusObserver.stopObserving(); + + // We're asked to stop throttling, so reset brightness restrictions. + mBrightnessCap = PowerManager.BRIGHTNESS_MAX; + mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + + // We set throttling status to an invalid value here so that we act on the first throttling + // value received from the thermal service after registration, even if that throttling value + // is THROTTLING_NONE. + mThrottlingStatus = THROTTLING_INVALID; + } + + void resetThrottlingData(BrightnessThrottlingData throttlingData) { + stop(); + mThrottlingData = throttlingData; + + if (deviceSupportsThrottling()) { + mSkinThermalStatusObserver.startObserving(); + } + } + + private float verifyAndConstrainBrightnessCap(float brightness) { + if (brightness < PowerManager.BRIGHTNESS_MIN) { + Slog.e(TAG, "brightness " + brightness + " is lower than the minimum possible " + + "brightness " + PowerManager.BRIGHTNESS_MIN); + brightness = PowerManager.BRIGHTNESS_MIN; + } + + if (brightness > PowerManager.BRIGHTNESS_MAX) { + Slog.e(TAG, "brightness " + brightness + " is higher than the maximum possible " + + "brightness " + PowerManager.BRIGHTNESS_MAX); + brightness = PowerManager.BRIGHTNESS_MAX; + } + + return brightness; + } + + private void thermalStatusChanged(@Temperature.ThrottlingStatus int newStatus) { + if (mThrottlingStatus != newStatus) { + mThrottlingStatus = newStatus; + updateThrottling(); + } + } + + private void updateThrottling() { + if (!deviceSupportsThrottling()) { + return; + } + + float brightnessCap = PowerManager.BRIGHTNESS_MAX; + int brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + + if (mThrottlingStatus != THROTTLING_INVALID) { + // Throttling levels are sorted by increasing severity + for (ThrottlingLevel level : mThrottlingData.throttlingLevels) { + if (level.thermalStatus <= mThrottlingStatus) { + brightnessCap = level.brightness; + brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; + } else { + // Throttling levels that are greater than the current status are irrelevant + break; + } + } + } + + if (mBrightnessCap != brightnessCap || mBrightnessMaxReason != brightnessMaxReason) { + mBrightnessCap = verifyAndConstrainBrightnessCap(brightnessCap); + mBrightnessMaxReason = brightnessMaxReason; + + if (DEBUG) { + Slog.d(TAG, "State changed: mBrightnessCap = " + mBrightnessCap + + ", mBrightnessMaxReason = " + + BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason)); + } + + if (mThrottlingChangeCallback != null) { + mThrottlingChangeCallback.run(); + } + } + } + + void dump(PrintWriter pw) { + mHandler.runWithScissors(() -> dumpLocal(pw), 1000); + } + + private void dumpLocal(PrintWriter pw) { + pw.println("BrightnessThrottler:"); + pw.println(" mThrottlingData=" + mThrottlingData); + pw.println(" mThrottlingStatus=" + mThrottlingStatus); + pw.println(" mBrightnessCap=" + mBrightnessCap); + pw.println(" mBrightnessMaxReason=" + + BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason)); + + mSkinThermalStatusObserver.dump(pw); + } + + private final class SkinThermalStatusObserver extends IThermalEventListener.Stub { + private final Injector mInjector; + private final Handler mHandler; + + private IThermalService mThermalService; + private boolean mStarted; + + SkinThermalStatusObserver(Injector injector, Handler handler) { + mInjector = injector; + mHandler = handler; + } + + @Override + public void notifyThrottling(Temperature temp) { + if (DEBUG) { + Slog.d(TAG, "New thermal throttling status = " + temp.getStatus()); + } + mHandler.post(() -> { + final @Temperature.ThrottlingStatus int status = temp.getStatus(); + thermalStatusChanged(status); + }); + } + + void startObserving() { + if (mStarted) { + if (DEBUG) { + Slog.d(TAG, "Thermal status observer already started"); + } + return; + } + mThermalService = mInjector.getThermalService(); + if (mThermalService == null) { + Slog.e(TAG, "Could not observe thermal status. Service not available"); + return; + } + try { + // We get a callback immediately upon registering so there's no need to query + // for the current value. + mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN); + mStarted = true; + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register thermal status listener", e); + } + } + + void stopObserving() { + if (!mStarted) { + if (DEBUG) { + Slog.d(TAG, "Stop skipped because thermal status observer not started"); + } + return; + } + try { + mThermalService.unregisterThermalEventListener(this); + mStarted = false; + } catch (RemoteException e) { + Slog.e(TAG, "Failed to unregister thermal status listener", e); + } + mThermalService = null; + } + + void dump(PrintWriter writer) { + writer.println(" SkinThermalStatusObserver:"); + writer.println(" mStarted: " + mStarted); + if (mThermalService != null) { + writer.println(" ThermalService available"); + } else { + writer.println(" ThermalService not available"); + } + } + } + + public static class Injector { + public IThermalService getThermalService() { + return IThermalService.Stub.asInterface( + ServiceManager.getService(Context.THERMAL_SERVICE)); + } + } +} diff --git a/services/core/java/com/android/server/display/BrightnessUtils.java b/services/core/java/com/android/server/display/BrightnessUtils.java new file mode 100644 index 000000000000..84fa0cccbd10 --- /dev/null +++ b/services/core/java/com/android/server/display/BrightnessUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.display; + +import android.util.MathUtils; + +/** + * Utility class providing functions to convert between linear and perceptual gamma space. + * + * Internally, this implements the Hybrid Log Gamma electro-optical transfer function, which is a + * slight improvement to the typical gamma transfer function for displays whose max brightness + * exceeds the 120 nit reference point, but doesn't set a specific reference brightness like the PQ + * function does. + * + * Note that this transfer function is only valid if the display's backlight value is a linear + * control. If it's calibrated to be something non-linear, then a different transfer function + * should be used. + * + * Note: This code is based on the same class in the com.android.settingslib.display package. + */ +public class BrightnessUtils { + + // Hybrid Log Gamma constant values + private static final float R = 0.5f; + private static final float A = 0.17883277f; + private static final float B = 0.28466892f; + private static final float C = 0.55991073f; + + /** + * A function for converting from the gamma space into the linear space. + * + * @param val The value in the gamma space [0 .. 1.0] + * @return The corresponding value in the linear space [0 .. 1.0]. + */ + public static final float convertGammaToLinear(float val) { + final float ret; + if (val <= R) { + ret = MathUtils.sq(val / R); + } else { + ret = MathUtils.exp((val - C) / A) + B; + } + + // HLG is normalized to the range [0, 12], ensure that value is within that range, + // it shouldn't be out of bounds. + final float normalizedRet = MathUtils.constrain(ret, 0, 12); + + // Re-normalize to the range [0, 1] + // in order to derive the correct setting value. + return normalizedRet / 12; + } + + /** + * A function for converting from the linear space into the gamma space. + * + * @param val The value in linear space [0 .. 1.0] + * @return The corresponding value in gamma space [0 .. 1.0] + */ + public static final float convertLinearToGamma(float val) { + // For some reason, HLG normalizes to the range [0, 12] rather than [0, 1] + final float normalizedVal = val * 12; + final float ret; + if (normalizedVal <= 1f) { + ret = MathUtils.sqrt(normalizedVal) * R; + } else { + ret = A * MathUtils.log(normalizedVal - B) + C; + } + return ret; + } +} diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index f3969b19e11f..a4a6eb4312cc 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -33,6 +33,8 @@ import android.view.DisplayAddress; import com.android.internal.R; import com.android.internal.display.BrightnessSynchronizer; import com.android.server.display.config.BrightnessThresholds; +import com.android.server.display.config.BrightnessThrottlingMap; +import com.android.server.display.config.BrightnessThrottlingPoint; import com.android.server.display.config.Density; import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; @@ -43,6 +45,7 @@ import com.android.server.display.config.Point; import com.android.server.display.config.RefreshRateRange; import com.android.server.display.config.SensorDetails; import com.android.server.display.config.ThermalStatus; +import com.android.server.display.config.ThermalThrottling; import com.android.server.display.config.Thresholds; import com.android.server.display.config.XmlParser; @@ -145,6 +148,8 @@ public class DisplayDeviceConfig { private DensityMap mDensityMap; private String mLoadedFrom = null; + private BrightnessThrottlingData mBrightnessThrottlingData; + private DisplayDeviceConfig(Context context) { mContext = context; } @@ -424,6 +429,13 @@ public class DisplayDeviceConfig { return mDensityMap; } + /** + * @return brightness throttling data configuration data for the display. + */ + public BrightnessThrottlingData getBrightnessThrottlingData() { + return BrightnessThrottlingData.create(mBrightnessThrottlingData); + } + @Override public String toString() { return "DisplayDeviceConfig{" @@ -441,6 +453,7 @@ public class DisplayDeviceConfig { + ", mQuirks=" + mQuirks + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled + ", mHbmData=" + mHbmData + + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease @@ -502,6 +515,7 @@ public class DisplayDeviceConfig { loadBrightnessDefaultFromDdcXml(config); loadBrightnessConstraintsFromConfigXml(); loadBrightnessMap(config); + loadBrightnessThrottlingMap(config); loadHighBrightnessModeData(config); loadQuirks(config); loadBrightnessRamps(config); @@ -664,6 +678,41 @@ public class DisplayDeviceConfig { constrainNitsAndBacklightArrays(); } + private void loadBrightnessThrottlingMap(DisplayConfiguration config) { + final ThermalThrottling throttlingConfig = config.getThermalThrottling(); + if (throttlingConfig == null) { + Slog.i(TAG, "no thermal throttling config found"); + return; + } + + final BrightnessThrottlingMap map = throttlingConfig.getBrightnessThrottlingMap(); + if (map == null) { + Slog.i(TAG, "no brightness throttling map found"); + return; + } + + final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint(); + // At least 1 point is guaranteed by the display device config schema + List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels = + new ArrayList<>(points.size()); + + boolean badConfig = false; + for (BrightnessThrottlingPoint point : points) { + ThermalStatus status = point.getThermalStatus(); + if (!thermalStatusIsValid(status)) { + badConfig = true; + break; + } + + throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel( + convertThermalStatus(status), point.getBrightness().floatValue())); + } + + if (!badConfig) { + mBrightnessThrottlingData = BrightnessThrottlingData.create(throttlingLevels); + } + } + private void loadBrightnessMapFromConfigXml() { // Use the config.xml mapping final Resources res = mContext.getResources(); @@ -931,6 +980,25 @@ public class DisplayDeviceConfig { } } + private boolean thermalStatusIsValid(ThermalStatus value) { + if (value == null) { + return false; + } + + switch (value) { + case none: + case light: + case moderate: + case severe: + case critical: + case emergency: + case shutdown: + return true; + default: + return false; + } + } + private @PowerManager.ThermalStatus int convertThermalStatus(ThermalStatus value) { if (value == null) { return PowerManager.THERMAL_STATUS_NONE; @@ -1061,4 +1129,91 @@ public class DisplayDeviceConfig { + "} "; } } + + /** + * Container for brightness throttling data. + */ + static class BrightnessThrottlingData { + static class ThrottlingLevel { + public @PowerManager.ThermalStatus int thermalStatus; + public float brightness; + + ThrottlingLevel(@PowerManager.ThermalStatus int thermalStatus, float brightness) { + this.thermalStatus = thermalStatus; + this.brightness = brightness; + } + + @Override + public String toString() { + return "[" + thermalStatus + "," + brightness + "]"; + } + } + + public List<ThrottlingLevel> throttlingLevels; + + static public BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels) + { + if (throttlingLevels == null || throttlingLevels.size() == 0) { + Slog.e(TAG, "BrightnessThrottlingData received null or empty throttling levels"); + return null; + } + + ThrottlingLevel prevLevel = throttlingLevels.get(0); + final int numLevels = throttlingLevels.size(); + for (int i = 1; i < numLevels; i++) { + ThrottlingLevel thisLevel = throttlingLevels.get(i); + + if (thisLevel.thermalStatus <= prevLevel.thermalStatus) { + Slog.e(TAG, "brightnessThrottlingMap must be strictly increasing, ignoring " + + "configuration. ThermalStatus " + thisLevel.thermalStatus + " <= " + + prevLevel.thermalStatus); + return null; + } + + if (thisLevel.brightness >= prevLevel.brightness) { + Slog.e(TAG, "brightnessThrottlingMap must be strictly decreasing, ignoring " + + "configuration. Brightness " + thisLevel.brightness + " >= " + + thisLevel.brightness); + return null; + } + + prevLevel = thisLevel; + } + + for (ThrottlingLevel level : throttlingLevels) { + // Non-negative brightness values are enforced by device config schema + if (level.brightness > PowerManager.BRIGHTNESS_MAX) { + Slog.e(TAG, "brightnessThrottlingMap contains a brightness value exceeding " + + "system max. Brightness " + level.brightness + " > " + + PowerManager.BRIGHTNESS_MAX); + return null; + } + } + + return new BrightnessThrottlingData(throttlingLevels); + } + + static public BrightnessThrottlingData create(BrightnessThrottlingData other) { + if (other == null) + return null; + + return BrightnessThrottlingData.create(other.throttlingLevels); + } + + + @Override + public String toString() { + return "BrightnessThrottlingData{" + + "throttlingLevels:" + throttlingLevels + + "} "; + } + + private BrightnessThrottlingData(List<ThrottlingLevel> inLevels) { + throttlingLevels = new ArrayList<>(inLevels.size()); + for (ThrottlingLevel level : inLevels) { + throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness)); + } + } + + } } 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/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index a9a1f08c140a..a9875c873bbb 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -335,7 +335,7 @@ class DisplayManagerShellCommand extends ShellCommand { } private int setUserDisabledHdrTypes() { - final String[] userDisabledHdrTypesText = getAllArgs(); + String[] userDisabledHdrTypesText = peekRemainingArgs(); if (userDisabledHdrTypesText == null) { getErrPrintWriter().println("Error: no userDisabledHdrTypes specified"); return 1; @@ -351,7 +351,6 @@ class DisplayManagerShellCommand extends ShellCommand { getErrPrintWriter().println("Error: invalid format of userDisabledHdrTypes"); return 1; } - final Context context = mService.getContext(); final DisplayManager dm = context.getSystemService(DisplayManager.class); dm.setUserDisabledHdrTypes(userDisabledHdrTypes); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index ec4b91a79ae6..1f448549c59c 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -346,6 +346,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private boolean mAppliedTemporaryBrightness; private boolean mAppliedTemporaryAutoBrightnessAdjustment; private boolean mAppliedBrightnessBoost; + private boolean mAppliedThrottling; // Reason for which the brightness was last changed. See {@link BrightnessReason} for more // information. @@ -379,6 +380,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final HighBrightnessModeController mHbmController; + private final BrightnessThrottler mBrightnessThrottler; + private final BrightnessSetting mBrightnessSetting; private final Runnable mOnBrightnessChangeRunnable; @@ -538,6 +541,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mHbmController = createHbmControllerLocked(); + mBrightnessThrottler = createBrightnessThrottlerLocked(); + // Seed the cached brightness saveBrightnessInfo(getScreenBrightnessSetting()); @@ -827,6 +832,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call reloadReduceBrightColours(); mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, mDisplayDeviceConfig.getHighBrightnessModeData()); + mBrightnessThrottler.resetThrottlingData( + mDisplayDeviceConfig.getBrightnessThrottlingData()); } private void sendUpdatePowerState() { @@ -1039,6 +1046,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void cleanupHandlerThreadAfterStop() { setProximitySensorEnabled(false); mHbmController.stop(); + mBrightnessThrottler.stop(); mHandler.removeCallbacksAndMessages(null); if (mUnfinishedBusiness) { mCallbacks.releaseSuspendBlocker(); @@ -1336,14 +1344,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL); } - // The current brightness to use has been calculated at this point (minus the adjustments - // like low-power and dim), 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 brightness sources (just as an app - // override) are not saved to the setting, but should be reflected in HBM - // calculations. - mHbmController.onBrightnessChanged(brightnessState); - 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,6 +1390,28 @@ 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 + // brightness sources (such as an app override) are not saved to the setting, but should be + // reflected in HBM calculations. + mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState, + mBrightnessThrottler.getBrightnessMaxReason()); + // Animate the screen brightness when the screen is on or dozing. // Skip the animation when the screen is off or suspended or transition to/from VR. boolean brightnessAdjusted = false; @@ -1441,6 +1463,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // use instead. We still preserve the calculated brightness for Standard Dynamic Range // (SDR) layers, but the main brightness value will be the one for HDR. float sdrAnimateValue = animateValue; + // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be + // done in HighBrightnessModeController. if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR && ((mBrightnessReason.modifier & BrightnessReason.MODIFIER_DIMMED) == 0 || (mBrightnessReason.modifier & BrightnessReason.MODIFIER_LOW_POWER) == 0)) { @@ -1618,7 +1642,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mCachedBrightnessInfo.brightnessMin.value, mCachedBrightnessInfo.brightnessMax.value, mCachedBrightnessInfo.hbmMode.value, - mCachedBrightnessInfo.hbmTransitionPoint.value); + mCachedBrightnessInfo.hbmTransitionPoint.value, + mCachedBrightnessInfo.brightnessMaxReason.value); } } @@ -1648,6 +1673,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call changed |= mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint, mHbmController.getTransitionPoint()); + changed |= + mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason, + mBrightnessThrottler.getBrightnessMaxReason()); return changed; } @@ -1679,6 +1707,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call }, mContext); } + private BrightnessThrottler createBrightnessThrottlerLocked() { + final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); + final DisplayDeviceConfig.BrightnessThrottlingData data = + ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null; + return new BrightnessThrottler(mHandler, data, + () -> { + sendUpdatePowerStateLocked(); + postBrightnessChangeRunnable(); + }); + } + private void blockScreenOn() { if (mPendingScreenOnUnblocker == null) { Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0); @@ -2346,6 +2386,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value); pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" + mCachedBrightnessInfo.hbmTransitionPoint.value); + pw.println(" mCachedBrightnessInfo.brightnessMaxReason =" + + mCachedBrightnessInfo.brightnessMaxReason .value); } pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig); pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig); @@ -2384,6 +2426,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness); pw.println(" mAppliedDimming=" + mAppliedDimming); pw.println(" mAppliedLowPower=" + mAppliedLowPower); + pw.println(" mAppliedThrottling=" + mAppliedThrottling); pw.println(" mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride); pw.println(" mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness); pw.println(" mDozing=" + mDozing); @@ -2422,6 +2465,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mHbmController.dump(pw); } + if (mBrightnessThrottler != null) { + mBrightnessThrottler.dump(pw); + } + pw.println(); if (mDisplayWhiteBalanceController != null) { mDisplayWhiteBalanceController.dump(pw); @@ -2702,7 +2749,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call static final int MODIFIER_DIMMED = 0x1; static final int MODIFIER_LOW_POWER = 0x2; static final int MODIFIER_HDR = 0x4; - static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR; + static final int MODIFIER_THROTTLED = 0x8; + static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR + | MODIFIER_THROTTLED; // ADJUSTMENT_* // These things can happen at any point, even if the main brightness reason doesn't @@ -2777,6 +2826,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if ((modifier & MODIFIER_HDR) != 0) { sb.append(" hdr"); } + if ((modifier & MODIFIER_THROTTLED) != 0) { + sb.append(" throttled"); + } int strlen = sb.length(); if (sb.charAt(strlen - 1) == '[') { sb.setLength(strlen - 2); @@ -2813,6 +2865,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF); public MutableFloat hbmTransitionPoint = new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID); + public MutableInt brightnessMaxReason = + new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE); public boolean checkAndSetFloat(MutableFloat mf, float f) { if (mf.value != f) { diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index b3be894b9510..23c17f5af10d 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -82,7 +82,15 @@ class HighBrightnessModeController { private boolean mIsTimeAvailable = false; private boolean mIsAutoBrightnessEnabled = false; private boolean mIsAutoBrightnessOffByState = false; + + // The following values are typically reported by DisplayPowerController. + // This value includes brightness throttling effects. private float mBrightness; + // This value excludes brightness throttling effects. + private float mUnthrottledBrightness; + private @BrightnessInfo.BrightnessMaxReason int mThrottlingReason = + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; private boolean mIsHdrLayerPresent = false; private boolean mIsThermalStatusWithinLimit = true; @@ -192,11 +200,14 @@ class HighBrightnessModeController { } } - void onBrightnessChanged(float brightness) { + void onBrightnessChanged(float brightness, float unthrottledBrightness, + @BrightnessInfo.BrightnessMaxReason int throttlingReason) { if (!deviceSupportsHbm()) { return; } mBrightness = brightness; + mUnthrottledBrightness = unthrottledBrightness; + mThrottlingReason = throttlingReason; // If we are starting or ending a high brightness mode session, store the current // session in mRunningStartTimeMillis, or the old one in mEvents. @@ -274,6 +285,8 @@ class HighBrightnessModeController { private void dumpLocal(PrintWriter pw) { pw.println("HighBrightnessModeController:"); pw.println(" mBrightness=" + mBrightness); + pw.println(" mUnthrottledBrightness=" + mUnthrottledBrightness); + pw.println(" mThrottlingReason=" + BrightnessInfo.briMaxReasonToString(mThrottlingReason)); pw.println(" mCurrentMin=" + getCurrentBrightnessMin()); pw.println(" mCurrentMax=" + getCurrentBrightnessMax()); pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode) @@ -431,6 +444,9 @@ class HighBrightnessModeController { + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode + ", mBrightness: " + mBrightness + + ", mUnthrottledBrightness: " + mUnthrottledBrightness + + ", mThrottlingReason: " + + BrightnessInfo.briMaxReasonToString(mThrottlingReason) + ", RunningStartTimeMillis: " + mRunningStartTimeMillis + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1) + ", events: " + mEvents); @@ -446,31 +462,44 @@ class HighBrightnessModeController { private void updateHbmMode() { int newHbmMode = calculateHighBrightnessMode(); - updateHbmStats(mHbmMode, newHbmMode); + updateHbmStats(newHbmMode); if (mHbmMode != newHbmMode) { mHbmMode = newHbmMode; mHbmChangeCallback.run(); } } - private void updateHbmStats(int mode, int newMode) { + private void updateHbmStats(int newMode) { + final float transitionPoint = mHbmData.transitionPoint; int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF; if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR - && getHdrBrightnessValue() > mHbmData.transitionPoint) { + && getHdrBrightnessValue() > transitionPoint) { state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR; - } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) { + } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT + && mBrightness > transitionPoint) { state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT; } if (state == mHbmStatsState) { return; } - mHbmStatsState = state; int reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN; - boolean oldHbmSv = (mode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT); - boolean newHbmSv = (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT); + final boolean oldHbmSv = (mHbmStatsState + == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT); + final boolean newHbmSv = + (state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT); if (oldHbmSv && !newHbmSv) { + // HighBrightnessModeController (HBMC) currently supports throttling from two sources: + // 1. Internal, received from HBMC.SkinThermalStatusObserver.notifyThrottling() + // 2. External, received from HBMC.onBrightnessChanged() + // TODO(b/216373254): Deprecate internal throttling source + final boolean internalThermalThrottling = !mIsThermalStatusWithinLimit; + final boolean externalThermalThrottling = + mUnthrottledBrightness > transitionPoint && // We would've liked HBM brightness... + mBrightness <= transitionPoint && // ...but we got NBM, because of... + mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; // ...thermals. + // If more than one conditions are flipped and turn off HBM sunlight // visibility, only one condition will be reported to make it simple. if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) { @@ -483,7 +512,7 @@ class HighBrightnessModeController { reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP; } else if (!mIsTimeAvailable) { reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT; - } else if (!mIsThermalStatusWithinLimit) { + } else if (internalThermalThrottling || externalThermalThrottling) { reason = FrameworkStatsLog .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT; } else if (mIsHdrLayerPresent) { @@ -492,10 +521,15 @@ 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; } } mInjector.reportHbmStateChange(mDisplayStatsId, state, reason); + mHbmStatsState = state; } private String hbmStatsStateToString(int hbmStatsState) { @@ -572,7 +606,7 @@ class HighBrightnessModeController { >= ((float) (mWidth * mHeight) * HDR_PERCENT_OF_SCREEN_REQUIRED); // Calling the brightness update so that we can recalculate // brightness with HDR in mind. - onBrightnessChanged(mBrightness); + onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason); }); } } diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index ed3b15fb2661..1ebd1f5a535c 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -23,6 +23,8 @@ import android.view.Choreographer; /** * A custom animator that progressively updates a property value at * a given variable rate until it reaches a particular target value. + * The ramping at the given rate is done in the perceptual space using + * the HLG transfer functions. */ class RampAnimator<T> { private final T mObject; @@ -57,7 +59,9 @@ class RampAnimator<T> { * @param rate The convergence rate in units per second, or 0 to set the value immediately. * @return True if the target differs from the previous target. */ - public boolean animateTo(float target, float rate) { + public boolean animateTo(float targetLinear, float rate) { + // Convert the target from the linear into the HLG space. + final float target = BrightnessUtils.convertLinearToGamma(targetLinear); // Immediately jump to the target the first time. if (mFirstTime || rate <= 0) { @@ -156,7 +160,9 @@ class RampAnimator<T> { final float oldCurrentValue = mCurrentValue; mCurrentValue = mAnimatedValue; if (oldCurrentValue != mCurrentValue) { - mProperty.setValue(mObject, mCurrentValue); + // Convert value from HLG into linear space for the property. + final float linearCurrentVal = BrightnessUtils.convertGammaToLinear(mCurrentValue); + mProperty.setValue(mObject, linearCurrentVal); } if (mTargetValue != mCurrentValue) { postAnimationCallback(); @@ -201,14 +207,14 @@ class RampAnimator<T> { * If this is the first time the property is being set or if the rate is 0, * the value jumps directly to the target. * - * @param firstTarget The first target value. - * @param secondTarget The second target value. + * @param linearFirstTarget The first target value in linear space. + * @param linearSecondTarget The second target value in linear space. * @param rate The convergence rate in units per second, or 0 to set the value immediately. * @return True if either target differs from the previous target. */ - public boolean animateTo(float firstTarget, float secondTarget, float rate) { - final boolean firstRetval = mFirst.animateTo(firstTarget, rate); - final boolean secondRetval = mSecond.animateTo(secondTarget, rate); + public boolean animateTo(float linearFirstTarget, float linearSecondTarget, float rate) { + final boolean firstRetval = mFirst.animateTo(linearFirstTarget, rate); + final boolean secondRetval = mSecond.animateTo(linearSecondTarget, rate); return firstRetval && secondRetval; } 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/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/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 9a53d837f25b..80c83e97256e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -162,28 +162,30 @@ public abstract class InputMethodManagerInternal { } @Override - public List<InputMethodInfo> getInputMethodListAsUser(int userId) { + public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { return Collections.emptyList(); } @Override - public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) { + public List<InputMethodInfo> getEnabledInputMethodListAsUser( + @UserIdInt int userId) { return Collections.emptyList(); } @Override - public void onCreateInlineSuggestionsRequest(int userId, + public void onCreateInlineSuggestionsRequest(@UserIdInt int userId, InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) { } @Override - public boolean switchToInputMethod(String imeId, int userId) { + public boolean switchToInputMethod(String imeId, @UserIdInt int userId) { return false; } @Override - public boolean setInputMethodEnabled(String imeId, boolean enabled, int userId) { + public boolean setInputMethodEnabled(String imeId, boolean enabled, + @UserIdInt int userId) { return false; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index c6a206bfff52..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); @@ -4977,28 +5003,28 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public List<InputMethodInfo> getInputMethodListAsUser(int userId) { + public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { return mService.getInputMethodListAsUser(userId); } @Override - public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) { + public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) { return mService.getEnabledInputMethodListAsUser(userId); } @Override - public void onCreateInlineSuggestionsRequest(int userId, + public void onCreateInlineSuggestionsRequest(@UserIdInt int userId, InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) { mService.onCreateInlineSuggestionsRequest(userId, requestInfo, cb); } @Override - public boolean switchToInputMethod(String imeId, int userId) { + public boolean switchToInputMethod(String imeId, @UserIdInt int userId) { return mService.switchToInputMethod(imeId, userId); } @Override - public boolean setInputMethodEnabled(String imeId, boolean enabled, int userId) { + public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) { return mService.setInputMethodEnabled(imeId, enabled, userId); } 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 ffef80356a66..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)); @@ -1239,29 +1232,29 @@ public class LocationManagerService extends ILocationManager.Stub implements } @Override - @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS) - public void setAutoGnssSuspended(boolean suspended) { - mContext.enforceCallingPermission(permission.AUTOMOTIVE_GNSS_CONTROLS, null); + @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) + public void setAutomotiveGnssSuspended(boolean suspended) { + mContext.enforceCallingPermission(permission.CONTROL_AUTOMOTIVE_GNSS, null); if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { throw new IllegalStateException( - "setAutoGnssSuspended only allowed on automotive devices"); + "setAutomotiveGnssSuspended only allowed on automotive devices"); } - mGnssManagerService.setAutoGnssSuspended(suspended); + mGnssManagerService.setAutomotiveGnssSuspended(suspended); } @Override - @RequiresPermission(android.Manifest.permission.AUTOMOTIVE_GNSS_CONTROLS) - public boolean isAutoGnssSuspended() { - mContext.enforceCallingPermission(permission.AUTOMOTIVE_GNSS_CONTROLS, null); + @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) + public boolean isAutomotiveGnssSuspended() { + mContext.enforceCallingPermission(permission.CONTROL_AUTOMOTIVE_GNSS, null); if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { throw new IllegalStateException( - "isAutoGnssSuspended only allowed on automotive devices"); + "isAutomotiveGnssSuspended only allowed on automotive devices"); } - return mGnssManagerService.isAutoGnssSuspended(); + return mGnssManagerService.isAutomotiveGnssSuspended(); } @Override 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/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index c02411ec4c1b..cd2ba393f6f2 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -753,7 +753,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements * Set whether the GnssLocationProvider is suspended. This method was added to help support * power management use cases on automotive devices. */ - public void setAutoGnssSuspended(boolean suspended) { + public void setAutomotiveGnssSuspended(boolean suspended) { synchronized (mLock) { mAutomotiveSuspend = suspended; } @@ -764,7 +764,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements * Return whether the GnssLocationProvider is suspended or not. This method was added to help * support power management use cases on automotive devices. */ - public boolean isAutoGnssSuspended() { + public boolean isAutomotiveGnssSuspended() { synchronized (mLock) { return mAutomotiveSuspend && !mGpsEnabled; } diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index 11fd727072df..0f9945ccdc6e 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -113,16 +113,16 @@ public class GnssManagerService { * Set whether the GnssLocationProvider is suspended on the device. This method was added to * help support power management use cases on automotive devices. */ - public void setAutoGnssSuspended(boolean suspended) { - mGnssLocationProvider.setAutoGnssSuspended(suspended); + public void setAutomotiveGnssSuspended(boolean suspended) { + mGnssLocationProvider.setAutomotiveGnssSuspended(suspended); } /** * Return whether the GnssLocationProvider is suspended or not. This method was added to * help support power management use cases on automotive devices. */ - public boolean isAutoGnssSuspended() { - return mGnssLocationProvider.isAutoGnssSuspended(); + public boolean isAutomotiveGnssSuspended() { + return mGnssLocationProvider.isAutomotiveGnssSuspended(); } /** Retrieve the IGpsGeofenceHardware. */ 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/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/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index fac7a40b17a7..8ce67a657740 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -98,8 +98,6 @@ import static android.net.NetworkStats.METERED_YES; import static android.net.NetworkTemplate.MATCH_CARRIER; import static android.net.NetworkTemplate.MATCH_MOBILE; import static android.net.NetworkTemplate.MATCH_WIFI; -import static android.net.NetworkTemplate.buildTemplateCarrierMetered; -import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; import static android.os.Trace.TRACE_TAG_NETWORK; import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED; @@ -168,7 +166,6 @@ import android.net.ConnectivityManager.NetworkCallback; import android.net.INetworkManagementEventObserver; import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; -import android.net.INetworkStatsService; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkIdentity; @@ -1324,7 +1321,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { }; /** - * Check {@link NetworkPolicy} against current {@link INetworkStatsService} + * Check {@link NetworkPolicy} against current {@link NetworkStatsManager} * to show visible notifications as needed. */ @GuardedBy("mNetworkPoliciesSecondLock") @@ -2322,6 +2319,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } /** + * Template to match all metered carrier networks with the given IMSI. + * + * @hide + */ + public static NetworkTemplate buildTemplateCarrierMetered(@NonNull String subscriberId) { + Objects.requireNonNull(subscriberId); + return new NetworkTemplate.Builder(MATCH_CARRIER) + .setSubscriberIds(Set.of(subscriberId)) + .setMeteredness(METERED_YES).build(); + } + + /** * Update the given {@link NetworkPolicy} based on any carrier-provided * defaults via {@link SubscriptionPlan} or {@link CarrierConfigManager}. * Leaves policy untouched if the user has modified it. @@ -2724,7 +2733,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { out.startTag(null, TAG_NETWORK_POLICY); writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule()); - final String subscriberId = template.getSubscriberId(); + final String subscriberId = template.getSubscriberIds().isEmpty() ? null + : template.getSubscriberIds().iterator().next(); if (subscriberId != null) { out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId); } @@ -3101,7 +3111,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } // When two normalized templates conflict, prefer the most // restrictive policy - policy.template = NetworkTemplate.normalize(policy.template, mMergedSubscriberIds); + policy.template = normalizeTemplate(policy.template, mMergedSubscriberIds); final NetworkPolicy existing = mNetworkPolicy.get(policy.template); if (existing == null || existing.compareTo(policy) > 0) { if (existing != null) { @@ -3112,6 +3122,46 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } + /** + * Examine the given template and normalize it. + * We pick the "lowest" merged subscriber as the primary + * for key purposes, and expand the template to match all other merged + * subscribers. + * + * There can be multiple merged subscriberIds for multi-SIM devices. + * + * <p> + * For example, given an incoming template matching B, and the currently + * active merge set [A,B], we'd return a new template that primarily matches + * A, but also matches B. + */ + private static NetworkTemplate normalizeTemplate(@NonNull NetworkTemplate template, + @NonNull List<String[]> mergedList) { + // Now there are several types of network which uses Subscriber Id to store network + // information. For instance: + // 1. A merged carrier wifi network which has TYPE_WIFI with a Subscriber Id. + // 2. A typical cellular network could have TYPE_MOBILE with a Subscriber Id. + + if (template.getSubscriberIds().isEmpty()) return template; + + for (final String[] merged : mergedList) { + // TODO: Handle incompatible subscriberIds if that happens in practice. + for (final String subscriberId : template.getSubscriberIds()) { + if (com.android.net.module.util.CollectionUtils.contains(merged, subscriberId)) { + // Requested template subscriber is part of the merged group; return + // a template that matches all merged subscribers. + return new NetworkTemplate.Builder(template.getMatchRule()) + .setWifiNetworkKeys(template.getWifiNetworkKeys()) + .setSubscriberIds(Set.of(merged)) + .setMeteredness(template.getMeteredness()) + .build(); + } + } + } + + return template; + } + @Override public void snoozeLimit(NetworkTemplate template) { mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); @@ -5559,7 +5609,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { NetworkPolicy[] policies = getNetworkPolicies(mContext.getOpPackageName()); NetworkTemplate templateCarrier = subscriber != null ? buildTemplateCarrierMetered(subscriber) : null; - NetworkTemplate templateMobile = buildTemplateMobileAll(subscriber); + NetworkTemplate templateMobile = subscriber != null + ? new NetworkTemplate.Builder(MATCH_MOBILE) + .setSubscriberIds(Set.of(subscriber)) + .setMeteredness(android.net.NetworkStats.METERED_YES) + .build() : null; for (NetworkPolicy policy : policies) { // All policies loaded from disk will be carrier templates, and setting will also only // set carrier templates, but we clear mobile templates just in case one is set by 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/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 99f70b206b65..4b592062c888 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -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; } 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/LowPowerStandbyController.java b/services/core/java/com/android/server/power/LowPowerStandbyController.java new file mode 100644 index 000000000000..cea84b57377c --- /dev/null +++ b/services/core/java/com/android/server/power/LowPowerStandbyController.java @@ -0,0 +1,608 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.app.AlarmManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.SystemClock; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.IndentingPrintWriter; +import android.util.Slog; +import android.util.SparseBooleanArray; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; + +import java.io.PrintWriter; +import java.util.Arrays; + +/** + * Controls Low Power Standby state. + * + * Instantiated by {@link PowerManagerService} only if Low Power Standby is supported. + * + * <p>Low Power Standby is active when all of the following conditions are met: + * <ul> + * <li>Low Power Standby is enabled + * <li>The device is not interactive, and has been non-interactive for a given timeout + * <li>The device is not in a doze maintenance window + * </ul> + * + * <p>When Low Power Standby is active, the following restrictions are applied to applications + * with procstate less important than {@link android.app.ActivityManager#PROCESS_STATE_BOUND_TOP}: + * <ul> + * <li>Network access is blocked + * <li>Wakelocks are disabled + * </ul> + * + * @hide + */ +public final class LowPowerStandbyController { + private static final String TAG = "LowPowerStandbyController"; + private static final boolean DEBUG = false; + private static final boolean DEFAULT_ACTIVE_DURING_MAINTENANCE = false; + + private static final int MSG_STANDBY_TIMEOUT = 0; + private static final int MSG_NOTIFY_ACTIVE_CHANGED = 1; + private static final int MSG_NOTIFY_ALLOWLIST_CHANGED = 2; + + private final Handler mHandler; + private final SettingsObserver mSettingsObserver; + private final Object mLock = new Object(); + + private final Context mContext; + private final Clock mClock; + private final AlarmManager.OnAlarmListener mOnStandbyTimeoutExpired = + this::onStandbyTimeoutExpired; + private final LowPowerStandbyControllerInternal mLocalService = new LocalService(); + private final SparseBooleanArray mAllowlistUids = new SparseBooleanArray(); + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case Intent.ACTION_SCREEN_OFF: + onNonInteractive(); + break; + case Intent.ACTION_SCREEN_ON: + onInteractive(); + break; + case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: + onDeviceIdleModeChanged(); + break; + } + } + }; + + @GuardedBy("mLock") + private AlarmManager mAlarmManager; + @GuardedBy("mLock") + private PowerManager mPowerManager; + @GuardedBy("mLock") + private boolean mSupportedConfig; + @GuardedBy("mLock") + private boolean mEnabledByDefaultConfig; + @GuardedBy("mLock") + private int mStandbyTimeoutConfig; + + /** Whether Low Power Standby is enabled in Settings */ + @GuardedBy("mLock") + private boolean mIsEnabled; + + /** + * Whether Low Power Standby is currently active (enforcing restrictions). + */ + @GuardedBy("mLock") + private boolean mIsActive; + + /** Whether the device is currently interactive */ + @GuardedBy("mLock") + private boolean mIsInteractive; + + /** The time the device was last interactive, in {@link SystemClock#elapsedRealtime()}. */ + @GuardedBy("mLock") + private long mLastInteractiveTimeElapsed; + + /** + * Whether we are in device idle mode. + * During maintenance windows Low Power Standby is deactivated to allow + * apps to run maintenance tasks. + */ + @GuardedBy("mLock") + private boolean mIsDeviceIdle; + + /** + * Whether the device has entered idle mode since becoming non-interactive. + * In the initial non-idle period after turning the screen off, Low Power Standby is already + * allowed to become active. Later non-idle periods are treated as maintenance windows, during + * which Low Power Standby is deactivated to allow apps to run maintenance tasks. + */ + @GuardedBy("mLock") + private boolean mIdleSinceNonInteractive; + + /** Whether Low Power Standby restrictions should be active during doze maintenance mode. */ + @GuardedBy("mLock") + private boolean mActiveDuringMaintenance; + + /** Force Low Power Standby to be active. */ + @GuardedBy("mLock") + private boolean mForceActive; + + /** Functional interface for providing time. */ + @VisibleForTesting + interface Clock { + /** Returns milliseconds since boot, including time spent in sleep. */ + long elapsedRealtime(); + } + + public LowPowerStandbyController(Context context, Looper looper, Clock clock) { + mContext = context; + mHandler = new LowPowerStandbyHandler(looper); + mClock = clock; + mSettingsObserver = new SettingsObserver(mHandler); + } + + void systemReady() { + final Resources resources = mContext.getResources(); + synchronized (mLock) { + mSupportedConfig = resources.getBoolean( + com.android.internal.R.bool.config_lowPowerStandbySupported); + + if (!mSupportedConfig) { + return; + } + + mAlarmManager = mContext.getSystemService(AlarmManager.class); + mPowerManager = mContext.getSystemService(PowerManager.class); + + mStandbyTimeoutConfig = resources.getInteger( + R.integer.config_lowPowerStandbyNonInteractiveTimeout); + mEnabledByDefaultConfig = resources.getBoolean( + R.bool.config_lowPowerStandbyEnabledByDefault); + + mIsInteractive = mPowerManager.isInteractive(); + + mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( + Settings.Global.LOW_POWER_STANDBY_ENABLED), + false, mSettingsObserver, UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( + Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE), + false, mSettingsObserver, UserHandle.USER_ALL); + updateSettingsLocked(); + + if (mIsEnabled) { + registerBroadcastReceiver(); + } + } + + LocalServices.addService(LowPowerStandbyControllerInternal.class, mLocalService); + } + + @GuardedBy("mLock") + private void updateSettingsLocked() { + final ContentResolver resolver = mContext.getContentResolver(); + mIsEnabled = mSupportedConfig && Settings.Global.getInt(resolver, + Settings.Global.LOW_POWER_STANDBY_ENABLED, + mEnabledByDefaultConfig ? 1 : 0) != 0; + mActiveDuringMaintenance = Settings.Global.getInt(resolver, + Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE, + DEFAULT_ACTIVE_DURING_MAINTENANCE ? 1 : 0) != 0; + + updateActiveLocked(); + } + + @GuardedBy("mLock") + private void updateActiveLocked() { + final long now = mClock.elapsedRealtime(); + final boolean standbyTimeoutExpired = + (now - mLastInteractiveTimeElapsed) >= mStandbyTimeoutConfig; + final boolean maintenanceMode = mIdleSinceNonInteractive && !mIsDeviceIdle; + final boolean newActive = + mForceActive || (mIsEnabled && !mIsInteractive && standbyTimeoutExpired + && (!maintenanceMode || mActiveDuringMaintenance)); + if (DEBUG) { + Slog.d(TAG, "updateActiveLocked: mIsEnabled=" + mIsEnabled + ", mIsInteractive=" + + mIsInteractive + ", standbyTimeoutExpired=" + standbyTimeoutExpired + + ", mIdleSinceNonInteractive=" + mIdleSinceNonInteractive + ", mIsDeviceIdle=" + + mIsDeviceIdle + ", mActiveDuringMaintenance=" + mActiveDuringMaintenance + + ", mForceActive=" + mForceActive + ", mIsActive=" + mIsActive + ", newActive=" + + newActive); + } + if (mIsActive != newActive) { + mIsActive = newActive; + if (DEBUG) { + Slog.d(TAG, "mIsActive changed, mIsActive=" + mIsActive); + } + enqueueNotifyActiveChangedLocked(); + } + } + + private void onNonInteractive() { + if (DEBUG) { + Slog.d(TAG, "onNonInteractive"); + } + final long now = mClock.elapsedRealtime(); + synchronized (mLock) { + mIsInteractive = false; + mIsDeviceIdle = false; + mLastInteractiveTimeElapsed = now; + + if (mStandbyTimeoutConfig > 0) { + scheduleStandbyTimeoutAlarmLocked(); + } + + updateActiveLocked(); + } + } + + private void onInteractive() { + if (DEBUG) { + Slog.d(TAG, "onInteractive"); + } + + synchronized (mLock) { + cancelStandbyTimeoutAlarmLocked(); + mIsInteractive = true; + mIsDeviceIdle = false; + mIdleSinceNonInteractive = false; + updateActiveLocked(); + } + } + + @GuardedBy("mLock") + private void scheduleStandbyTimeoutAlarmLocked() { + final long nextAlarmTime = SystemClock.elapsedRealtime() + mStandbyTimeoutConfig; + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, + nextAlarmTime, "LowPowerStandbyController.StandbyTimeout", + mOnStandbyTimeoutExpired, mHandler); + } + + @GuardedBy("mLock") + private void cancelStandbyTimeoutAlarmLocked() { + mAlarmManager.cancel(mOnStandbyTimeoutExpired); + } + + private void onDeviceIdleModeChanged() { + synchronized (mLock) { + mIsDeviceIdle = mPowerManager.isDeviceIdleMode(); + if (DEBUG) { + Slog.d(TAG, "onDeviceIdleModeChanged, mIsDeviceIdle=" + mIsDeviceIdle); + } + + mIdleSinceNonInteractive = mIdleSinceNonInteractive || mIsDeviceIdle; + updateActiveLocked(); + } + } + + @GuardedBy("mLock") + private void onEnabledLocked() { + if (DEBUG) { + Slog.d(TAG, "onEnabledLocked"); + } + + if (mPowerManager.isInteractive()) { + onInteractive(); + } else { + onNonInteractive(); + } + + registerBroadcastReceiver(); + } + + @GuardedBy("mLock") + private void onDisabledLocked() { + if (DEBUG) { + Slog.d(TAG, "onDisabledLocked"); + } + + cancelStandbyTimeoutAlarmLocked(); + unregisterBroadcastReceiver(); + updateActiveLocked(); + } + + @VisibleForTesting + void onSettingsChanged() { + if (DEBUG) { + Slog.d(TAG, "onSettingsChanged"); + } + synchronized (mLock) { + final boolean oldEnabled = mIsEnabled; + updateSettingsLocked(); + + if (mIsEnabled != oldEnabled) { + if (mIsEnabled) { + onEnabledLocked(); + } else { + onDisabledLocked(); + } + + notifyEnabledChangedLocked(); + } + } + } + + private void registerBroadcastReceiver() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); + intentFilter.addAction(Intent.ACTION_SCREEN_ON); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + + mContext.registerReceiver(mBroadcastReceiver, intentFilter); + } + + private void unregisterBroadcastReceiver() { + mContext.unregisterReceiver(mBroadcastReceiver); + } + + @GuardedBy("mLock") + private void notifyEnabledChangedLocked() { + if (DEBUG) { + Slog.d(TAG, "notifyEnabledChangedLocked, mIsEnabled=" + mIsEnabled); + } + + final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + + private void onStandbyTimeoutExpired() { + if (DEBUG) { + Slog.d(TAG, "onStandbyTimeoutExpired"); + } + synchronized (mLock) { + updateActiveLocked(); + } + } + + @GuardedBy("mLock") + private void enqueueNotifyActiveChangedLocked() { + final long now = mClock.elapsedRealtime(); + final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ACTIVE_CHANGED, mIsActive); + mHandler.sendMessageAtTime(msg, now); + } + + /** Notify other system components about the updated Low Power Standby active state */ + private void notifyActiveChanged(boolean active) { + final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); + pmi.setLowPowerStandbyActive(active); + } + + @VisibleForTesting + boolean isActive() { + synchronized (mLock) { + return mIsActive; + } + } + + boolean isSupported() { + synchronized (mLock) { + return mSupportedConfig; + } + } + + boolean isEnabled() { + synchronized (mLock) { + return mSupportedConfig && mIsEnabled; + } + } + + void setEnabled(boolean enabled) { + synchronized (mLock) { + if (!mSupportedConfig) { + Slog.w(TAG, "Low Power Standby cannot be enabled " + + "because it is not supported on this device"); + return; + } + + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.LOW_POWER_STANDBY_ENABLED, enabled ? 1 : 0); + onSettingsChanged(); + } + } + + void setActiveDuringMaintenance(boolean activeDuringMaintenance) { + synchronized (mLock) { + if (!mSupportedConfig) { + Slog.w(TAG, "Low Power Standby settings cannot be changed " + + "because it is not supported on this device"); + return; + } + + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE, + activeDuringMaintenance ? 1 : 0); + onSettingsChanged(); + } + } + + void forceActive(boolean active) { + synchronized (mLock) { + mForceActive = active; + updateActiveLocked(); + } + } + + void dump(PrintWriter pw) { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + + ipw.println(); + ipw.println("Low Power Standby Controller:"); + ipw.increaseIndent(); + synchronized (mLock) { + ipw.print("mIsActive="); + ipw.println(mIsActive); + ipw.print("mIsEnabled="); + ipw.println(mIsEnabled); + ipw.print("mSupportedConfig="); + ipw.println(mSupportedConfig); + ipw.print("mEnabledByDefaultConfig="); + ipw.println(mEnabledByDefaultConfig); + ipw.print("mStandbyTimeoutConfig="); + ipw.println(mStandbyTimeoutConfig); + + if (mIsActive || mIsEnabled) { + ipw.print("mIsInteractive="); + ipw.println(mIsInteractive); + ipw.print("mLastInteractiveTime="); + ipw.println(mLastInteractiveTimeElapsed); + ipw.print("mIdleSinceNonInteractive="); + ipw.println(mIdleSinceNonInteractive); + ipw.print("mIsDeviceIdle="); + ipw.println(mIsDeviceIdle); + } + + final int[] allowlistUids = getAllowlistUidsLocked(); + ipw.print("mAllowlistUids="); + ipw.println(Arrays.toString(allowlistUids)); + } + ipw.decreaseIndent(); + } + + void dumpProto(ProtoOutputStream proto, long tag) { + synchronized (mLock) { + final long token = proto.start(tag); + proto.write(LowPowerStandbyControllerDumpProto.IS_ACTIVE, mIsActive); + proto.write(LowPowerStandbyControllerDumpProto.IS_ENABLED, mIsEnabled); + proto.write(LowPowerStandbyControllerDumpProto.IS_SUPPORTED_CONFIG, mSupportedConfig); + proto.write(LowPowerStandbyControllerDumpProto.IS_ENABLED_BY_DEFAULT_CONFIG, + mEnabledByDefaultConfig); + proto.write(LowPowerStandbyControllerDumpProto.IS_INTERACTIVE, mIsInteractive); + proto.write(LowPowerStandbyControllerDumpProto.LAST_INTERACTIVE_TIME, + mLastInteractiveTimeElapsed); + proto.write(LowPowerStandbyControllerDumpProto.STANDBY_TIMEOUT_CONFIG, + mStandbyTimeoutConfig); + proto.write(LowPowerStandbyControllerDumpProto.IDLE_SINCE_NON_INTERACTIVE, + mIdleSinceNonInteractive); + proto.write(LowPowerStandbyControllerDumpProto.IS_DEVICE_IDLE, mIsDeviceIdle); + + final int[] allowlistUids = getAllowlistUidsLocked(); + for (int appId : allowlistUids) { + proto.write(LowPowerStandbyControllerDumpProto.ALLOWLIST, appId); + } + + proto.end(token); + } + } + + private class LowPowerStandbyHandler extends Handler { + LowPowerStandbyHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_STANDBY_TIMEOUT: + onStandbyTimeoutExpired(); + break; + case MSG_NOTIFY_ACTIVE_CHANGED: + boolean active = (boolean) msg.obj; + notifyActiveChanged(active); + break; + case MSG_NOTIFY_ALLOWLIST_CHANGED: + final int[] allowlistUids = (int[]) msg.obj; + notifyAllowlistChanged(allowlistUids); + break; + } + } + } + + private void addToAllowlistInternal(int uid) { + if (DEBUG) { + Slog.i(TAG, "Adding to allowlist: " + uid); + } + synchronized (mLock) { + if (mSupportedConfig && !mAllowlistUids.get(uid)) { + mAllowlistUids.append(uid, true); + enqueueNotifyAllowlistChangedLocked(); + } + } + } + + private void removeFromAllowlistInternal(int uid) { + if (DEBUG) { + Slog.i(TAG, "Removing from allowlist: " + uid); + } + synchronized (mLock) { + if (mSupportedConfig && mAllowlistUids.get(uid)) { + mAllowlistUids.delete(uid); + enqueueNotifyAllowlistChangedLocked(); + } + } + } + + @GuardedBy("mLock") + private int[] getAllowlistUidsLocked() { + final int[] uids = new int[mAllowlistUids.size()]; + for (int i = 0; i < mAllowlistUids.size(); i++) { + uids[i] = mAllowlistUids.keyAt(i); + } + return uids; + } + + @GuardedBy("mLock") + private void enqueueNotifyAllowlistChangedLocked() { + final long now = mClock.elapsedRealtime(); + final int[] allowlistUids = getAllowlistUidsLocked(); + final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ALLOWLIST_CHANGED, allowlistUids); + mHandler.sendMessageAtTime(msg, now); + } + + private void notifyAllowlistChanged(int[] allowlistUids) { + final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); + pmi.setLowPowerStandbyAllowlist(allowlistUids); + } + + private final class LocalService extends LowPowerStandbyControllerInternal { + @Override + public void addToAllowlist(int uid) { + addToAllowlistInternal(uid); + } + + @Override + public void removeFromAllowlist(int uid) { + removeFromAllowlistInternal(uid); + } + } + + private final class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + onSettingsChanged(); + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl b/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java index 3d5998bffcbf..f6953faa8eec 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/communal/ICommunalSurfaceCallback.aidl +++ b/services/core/java/com/android/server/power/LowPowerStandbyControllerInternal.java @@ -14,17 +14,24 @@ * limitations under the License. */ -package com.android.systemui.shared.communal; - -import android.view.SurfaceControlViewHost.SurfacePackage; +package com.android.server.power; /** -* 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; -}
\ No newline at end of file + * @hide Only for use within the system server. + */ +public abstract class LowPowerStandbyControllerInternal { + /** + * Adds an application to the Low Power Standby allowlist, + * exempting it from Low Power Standby restrictions. + * + * @param uid UID to add to allowlist. + */ + public abstract void addToAllowlist(int uid); + + /** + * Removes an application from the Low Power Standby allowlist. + * + * @param uid UID to remove from allowlist. + */ + public abstract void removeFromAllowlist(int uid); +} diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 4185b2d9e497..efcfbdd16e4e 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -37,6 +37,7 @@ import static com.android.internal.util.LatencyTracker.ACTION_TURN_ON_SCREEN; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.SynchronousUserSwitchObserver; @@ -276,6 +277,7 @@ public final class PowerManagerService extends SystemService private final BatterySaverPolicy mBatterySaverPolicy; private final BatterySaverStateMachine mBatterySaverStateMachine; private final BatterySavingStats mBatterySavingStats; + private final LowPowerStandbyController mLowPowerStandbyController; private final AttentionDetector mAttentionDetector; private final FaceDownDetector mFaceDownDetector; private final ScreenUndimDetector mScreenUndimDetector; @@ -609,12 +611,17 @@ public final class PowerManagerService extends SystemService // True if we are currently in light device idle mode. private boolean mLightDeviceIdleMode; - // Set of app ids that we will always respect the wake locks for. + // Set of app ids that we will respect the wake locks for while in device idle mode. int[] mDeviceIdleWhitelist = new int[0]; // Set of app ids that are temporarily allowed to acquire wakelocks due to high-pri message int[] mDeviceIdleTempWhitelist = new int[0]; + // Set of app ids that are allowed to acquire wakelocks while low power standby is active + int[] mLowPowerStandbyAllowlist = new int[0]; + + private boolean mLowPowerStandbyActive; + private final SparseArray<UidState> mUidState = new SparseArray<>(); // A mapping from DisplayGroup Id to PowerGroup. There is a 1-1 mapping between DisplayGroups @@ -966,6 +973,10 @@ public final class PowerManagerService extends SystemService void invalidateIsInteractiveCaches() { PowerManager.invalidateIsInteractiveCaches(); } + + LowPowerStandbyController createLowPowerStandbyController(Context context, Looper looper) { + return new LowPowerStandbyController(context, looper, SystemClock::elapsedRealtime); + } } final Constants mConstants; @@ -1015,6 +1026,8 @@ public final class PowerManagerService extends SystemService mBatterySaverStateMachine = mInjector.createBatterySaverStateMachine(mLock, mContext, mBatterySaverController); + mLowPowerStandbyController = mInjector.createLowPowerStandbyController(mContext, + Looper.getMainLooper()); mInattentiveSleepWarningOverlayController = mInjector.createInattentiveSleepWarningController(); @@ -1228,6 +1241,8 @@ public final class PowerManagerService extends SystemService // Shouldn't happen since in-process. } + mLowPowerStandbyController.systemReady(); + // Go. readConfigurationLocked(); updateSettingsLocked(); @@ -1654,6 +1669,16 @@ public final class PowerManagerService extends SystemService } @GuardedBy("mLock") + @VisibleForTesting + WakeLock findWakeLockLocked(IBinder lock) { + int index = findWakeLockIndexLocked(lock); + if (index == -1) { + return null; + } + return mWakeLocks.get(index); + } + + @GuardedBy("mLock") private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) { if (mSystemReady && !wakeLock.mDisabled) { wakeLock.mNotifiedAcquired = true; @@ -3852,6 +3877,24 @@ public final class PowerManagerService extends SystemService } } + void setLowPowerStandbyAllowlistInternal(int[] appids) { + synchronized (mLock) { + mLowPowerStandbyAllowlist = appids; + if (mLowPowerStandbyActive) { + updateWakeLockDisabledStatesLocked(); + } + } + } + + void setLowPowerStandbyActiveInternal(boolean active) { + synchronized (mLock) { + if (mLowPowerStandbyActive != active) { + mLowPowerStandbyActive = active; + updateWakeLockDisabledStatesLocked(); + } + } + } + void startUidChangesInternal() { synchronized (mLock) { mUidsChanging = true; @@ -3888,7 +3931,7 @@ public final class PowerManagerService extends SystemService <= ActivityManager.PROCESS_STATE_RECEIVER; state.mProcState = procState; if (state.mNumWakeLocks > 0) { - if (mDeviceIdleMode) { + if (mDeviceIdleMode || mLowPowerStandbyActive) { handleUidStateChangeLocked(); } else if (!state.mActive && oldShouldAllow != (procState <= ActivityManager.PROCESS_STATE_RECEIVER)) { @@ -3908,7 +3951,7 @@ public final class PowerManagerService extends SystemService state.mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT; state.mActive = false; mUidState.removeAt(index); - if (mDeviceIdleMode && state.mNumWakeLocks > 0) { + if ((mDeviceIdleMode || mLowPowerStandbyActive) && state.mNumWakeLocks > 0) { handleUidStateChangeLocked(); } } @@ -3993,6 +4036,14 @@ public final class PowerManagerService extends SystemService disabled = true; } } + if (mLowPowerStandbyActive) { + final UidState state = wakeLock.mUidState; + if (Arrays.binarySearch(mLowPowerStandbyAllowlist, appid) < 0 + && state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT + && state.mProcState > ActivityManager.PROCESS_STATE_BOUND_TOP) { + disabled = true; + } + } } if (wakeLock.mDisabled != disabled) { wakeLock.mDisabled = disabled; @@ -4260,6 +4311,7 @@ public final class PowerManagerService extends SystemService pw.println("POWER MANAGER (dumpsys power)\n"); final WirelessChargerDetector wcd; + final LowPowerStandbyController lowPowerStandbyController; synchronized (mLock) { pw.println("Power Manager State:"); mConstants.dump(pw); @@ -4316,6 +4368,7 @@ public final class PowerManagerService extends SystemService pw.println(" mDeviceIdleMode=" + mDeviceIdleMode); pw.println(" mDeviceIdleWhitelist=" + Arrays.toString(mDeviceIdleWhitelist)); pw.println(" mDeviceIdleTempWhitelist=" + Arrays.toString(mDeviceIdleTempWhitelist)); + pw.println(" mLowPowerStandbyActive=" + mLowPowerStandbyActive); pw.println(" mLastWakeTime=" + TimeUtils.formatUptime(mLastGlobalWakeTime)); pw.println(" mLastSleepTime=" + TimeUtils.formatUptime(mLastGlobalSleepTime)); pw.println(" mLastSleepReason=" + PowerManager.sleepReasonToString( @@ -4491,10 +4544,13 @@ public final class PowerManagerService extends SystemService mFaceDownDetector.dump(pw); mAmbientDisplaySuppressionController.dump(pw); + + mLowPowerStandbyController.dump(pw); } private void dumpProto(FileDescriptor fd) { final WirelessChargerDetector wcd; + final LowPowerStandbyController lowPowerStandbyController; final ProtoOutputStream proto = new ProtoOutputStream(fd); synchronized (mLock) { @@ -4599,6 +4655,9 @@ public final class PowerManagerService extends SystemService proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id); } + proto.write(PowerManagerServiceDumpProto.IS_LOW_POWER_STANDBY_ACTIVE, + mLowPowerStandbyActive); + proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastGlobalWakeTime); proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastGlobalSleepTime); proto.write( @@ -4832,6 +4891,7 @@ public final class PowerManagerService extends SystemService for (SuspendBlocker sb : mSuspendBlockers) { sb.dumpDebug(proto, PowerManagerServiceDumpProto.SUSPEND_BLOCKERS); } + wcd = mWirelessChargerDetector; } @@ -4839,6 +4899,9 @@ public final class PowerManagerService extends SystemService wcd.dumpDebug(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR); } + mLowPowerStandbyController.dumpProto(proto, + PowerManagerServiceDumpProto.LOW_POWER_STANDBY_CONTROLLER); + proto.flush(); } @@ -5092,6 +5155,8 @@ public final class PowerManagerService extends SystemService (mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP)!=0); proto.write(WakeLockProto.WakeLockFlagsProto.IS_ON_AFTER_RELEASE, (mFlags & PowerManager.ON_AFTER_RELEASE)!=0); + proto.write(WakeLockProto.WakeLockFlagsProto.SYSTEM_WAKELOCK, + (mFlags & PowerManager.SYSTEM_WAKELOCK) != 0); proto.end(wakeLockFlagsToken); proto.write(WakeLockProto.IS_DISABLED, mDisabled); @@ -5138,6 +5203,9 @@ public final class PowerManagerService extends SystemService if ((mFlags & PowerManager.ON_AFTER_RELEASE) != 0) { result += " ON_AFTER_RELEASE"; } + if ((mFlags & PowerManager.SYSTEM_WAKELOCK) != 0) { + result += " SYSTEM_WAKELOCK"; + } return result; } } @@ -5299,8 +5367,22 @@ public final class PowerManagerService extends SystemService ws = null; } - final int uid = Binder.getCallingUid(); - final int pid = Binder.getCallingPid(); + int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); + + if ((flags & PowerManager.SYSTEM_WAKELOCK) != 0) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, + null); + WorkSource workSource = new WorkSource(Binder.getCallingUid(), packageName); + if (ws != null && !ws.isEmpty()) { + workSource.add(ws); + } + ws = workSource; + + uid = Process.myUid(); + pid = Process.myPid(); + } + final long ident = Binder.clearCallingIdentity(); try { acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag, @@ -5768,6 +5850,100 @@ public final class PowerManagerService extends SystemService } } + @Override // Binder call + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public boolean isLowPowerStandbySupported() { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + "isLowPowerStandbySupported"); + } + + final long ident = Binder.clearCallingIdentity(); + try { + return mLowPowerStandbyController.isSupported(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public boolean isLowPowerStandbyEnabled() { + final long ident = Binder.clearCallingIdentity(); + try { + return mLowPowerStandbyController.isEnabled(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public void setLowPowerStandbyEnabled(boolean enabled) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + "setLowPowerStandbyEnabled"); + } + + final long ident = Binder.clearCallingIdentity(); + try { + mLowPowerStandbyController.setEnabled(enabled); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public void setLowPowerStandbyActiveDuringMaintenance(boolean activeDuringMaintenance) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + "setLowPowerStandbyActiveDuringMaintenance"); + } + + final long ident = Binder.clearCallingIdentity(); + try { + mLowPowerStandbyController.setActiveDuringMaintenance(activeDuringMaintenance); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + android.Manifest.permission.DEVICE_POWER + }) + public void forceLowPowerStandbyActive(boolean active) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_LOW_POWER_STANDBY, + "forceLowPowerStandbyActive"); + } + + final long ident = Binder.clearCallingIdentity(); + try { + mLowPowerStandbyController.forceActive(active); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** * Gets the reason for the last time the phone had to reboot. * @@ -6249,6 +6425,16 @@ public final class PowerManagerService extends SystemService } @Override + public void setLowPowerStandbyAllowlist(int[] appids) { + setLowPowerStandbyAllowlistInternal(appids); + } + + @Override + public void setLowPowerStandbyActive(boolean enabled) { + setLowPowerStandbyActiveInternal(enabled); + } + + @Override public void startUidChanges() { startUidChangesInternal(); } diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java index 88c9850966f1..d20c7f1fe9c6 100644 --- a/services/core/java/com/android/server/power/WakeLockLog.java +++ b/services/core/java/com/android/server/power/WakeLockLog.java @@ -106,6 +106,7 @@ final class WakeLockLog { */ private static final int FLAG_ON_AFTER_RELEASE = 0x8; private static final int FLAG_ACQUIRE_CAUSES_WAKEUP = 0x10; + private static final int FLAG_SYSTEM_WAKELOCK = 0x20; private static final int MASK_LOWER_6_BITS = 0x3F; private static final int MASK_LOWER_7_BITS = 0x7F; @@ -296,6 +297,9 @@ final class WakeLockLog { if ((flags & PowerManager.ON_AFTER_RELEASE) != 0) { newFlags |= FLAG_ON_AFTER_RELEASE; } + if ((flags & PowerManager.SYSTEM_WAKELOCK) != 0) { + newFlags |= FLAG_SYSTEM_WAKELOCK; + } return newFlags; } @@ -455,6 +459,9 @@ final class WakeLockLog { if ((flags & FLAG_ACQUIRE_CAUSES_WAKEUP) == FLAG_ACQUIRE_CAUSES_WAKEUP) { sb.append(",acq-causes-wake"); } + if ((flags & FLAG_SYSTEM_WAKELOCK) == FLAG_SYSTEM_WAKELOCK) { + sb.append(",system-wakelock"); + } } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index e71ff784dc23..344edbbef9c7 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; @@ -894,6 +895,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override + public void setBiometicContextListener(IBiometricContextListener listener) { + enforceStatusBarService(); + if (mBar != null) { + try { + mBar.setBiometicContextListener(listener); + } catch (RemoteException ex) { + } + } + } + + @Override public void setUdfpsHbmListener(IUdfpsHbmListener listener) { enforceStatusBarService(); if (mBar != null) { diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java index ff2f08bc4a50..27c0beee9c27 100644 --- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java +++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java @@ -15,34 +15,56 @@ */ package com.android.server.tracing; +import android.Manifest; +import android.annotation.NonNull; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ServiceInfo; import android.os.Binder; +import android.os.IMessenger; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.ParcelFileDescriptor.AutoCloseInputStream; +import android.os.ParcelFileDescriptor.AutoCloseOutputStream; import android.os.UserHandle; +import android.service.tracing.TraceReportService; import android.tracing.ITracingServiceProxy; +import android.tracing.TraceReportParams; import android.util.Log; +import android.util.LruCache; +import android.util.Slog; +import com.android.internal.infra.ServiceConnector; import com.android.server.SystemService; +import java.io.IOException; + /** * TracingServiceProxy is the system_server intermediary between the Perfetto tracing daemon and the - * system tracing app Traceur. + * other components (e.g. system tracing app Traceur, trace reporting apps). * * Access to this service is restricted via SELinux. Normal apps do not have access. * * @hide */ public class TracingServiceProxy extends SystemService { - private static final String TAG = "TracingServiceProxy"; - public static final String TRACING_SERVICE_PROXY_BINDER_NAME = "tracing.proxy"; - + private static final String TAG = "TracingServiceProxy"; private static final String TRACING_APP_PACKAGE_NAME = "com.android.traceur"; private static final String TRACING_APP_ACTIVITY = "com.android.traceur.StopTraceService"; + private static final int MAX_CACHED_REPORTER_SERVICES = 8; + + // The maximum size of the trace allowed if the option |usePipeForTesting| is set. + // Note: this size MUST be smaller than the buffer size of the pipe (i.e. what you can + // write to the pipe without blocking) to avoid system_server blocking on this. + // (on Linux, the minimum value is 4K i.e. 1 minimally sized page). + private static final int MAX_FILE_SIZE_BYTES_TO_PIPE = 1024; + // Keep this in sync with the definitions in TraceService private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED = "com.android.traceur.NOTIFY_SESSION_STOPPED"; @@ -51,16 +73,22 @@ public class TracingServiceProxy extends SystemService { private final Context mContext; private final PackageManager mPackageManager; + private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices; private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() { /** - * Notifies system tracing app that a tracing session has ended. If a session is repurposed - * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but - * there is no buffer available to dump. - */ + * Notifies system tracing app that a tracing session has ended. If a session is repurposed + * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but + * there is no buffer available to dump. + */ @Override public void notifyTraceSessionEnded(boolean sessionStolen) { - notifyTraceur(sessionStolen); + TracingServiceProxy.this.notifyTraceur(sessionStolen); + } + + @Override + public void reportTrace(@NonNull TraceReportParams params) { + TracingServiceProxy.this.reportTrace(params); } }; @@ -68,6 +96,7 @@ public class TracingServiceProxy extends SystemService { super(context); mContext = context; mPackageManager = context.getPackageManager(); + mCachedReporterServices = new LruCache<>(MAX_CACHED_REPORTER_SERVICES); } @Override @@ -103,4 +132,119 @@ public class TracingServiceProxy extends SystemService { Log.e(TAG, "Failed to locate Traceur", e); } } + + private void reportTrace(@NonNull TraceReportParams params) { + // We don't need to do any permission checks on the caller because access + // to this service is guarded by SELinux. + ComponentName component = new ComponentName(params.reporterPackageName, + params.reporterClassName); + if (!hasBindServicePermission(component)) { + return; + } + boolean hasDumpPermission = hasPermission(component, Manifest.permission.DUMP); + boolean hasUsageStatsPermission = hasPermission(component, + Manifest.permission.PACKAGE_USAGE_STATS); + if (!hasDumpPermission || !hasUsageStatsPermission) { + return; + } + final long ident = Binder.clearCallingIdentity(); + try { + reportTrace(getOrCreateReporterService(component), params); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void reportTrace( + @NonNull ServiceConnector<IMessenger> reporterService, + @NonNull TraceReportParams params) { + reporterService.post(messenger -> { + if (params.usePipeForTesting) { + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + try (AutoCloseInputStream i = new AutoCloseInputStream(params.fd)) { + try (AutoCloseOutputStream o = new AutoCloseOutputStream(pipe[1])) { + byte[] array = i.readNBytes(MAX_FILE_SIZE_BYTES_TO_PIPE); + if (array.length == MAX_FILE_SIZE_BYTES_TO_PIPE) { + throw new IllegalArgumentException( + "Trace file too large when |usePipeForTesting| is set."); + } + o.write(array); + } + } + params.fd = pipe[0]; + } + + Message message = Message.obtain(); + message.what = TraceReportService.MSG_REPORT_TRACE; + message.obj = params; + messenger.send(message); + }).whenComplete((res, err) -> { + if (err != null) { + Slog.e(TAG, "Failed to report trace", err); + } + try { + params.fd.close(); + } catch (IOException ignored) { + } + }); + } + + private ServiceConnector<IMessenger> getOrCreateReporterService( + @NonNull ComponentName component) { + ServiceConnector<IMessenger> connector = mCachedReporterServices.get(component); + if (connector == null) { + Intent intent = new Intent(); + intent.setComponent(component); + connector = new ServiceConnector.Impl<IMessenger>( + mContext, intent, + Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY, + mContext.getUser().getIdentifier(), IMessenger.Stub::asInterface) { + private static final long DISCONNECT_TIMEOUT_MS = 15_000; + private static final long REQUEST_TIMEOUT_MS = 10_000; + + @Override + protected long getAutoDisconnectTimeoutMs() { + return DISCONNECT_TIMEOUT_MS; + } + + @Override + protected long getRequestTimeoutMs() { + return REQUEST_TIMEOUT_MS; + } + }; + mCachedReporterServices.put(intent.getComponent(), connector); + } + return connector; + } + + private boolean hasPermission(@NonNull ComponentName componentName, + @NonNull String permission) throws SecurityException { + if (mPackageManager.checkPermission(permission, componentName.getPackageName()) + != PackageManager.PERMISSION_GRANTED) { + Slog.e(TAG, + "Trace reporting service " + componentName.toShortString() + " does not have " + + permission + " permission"); + return false; + } + return true; + } + + private boolean hasBindServicePermission(@NonNull ComponentName componentName) { + ServiceInfo info; + try { + info = mPackageManager.getServiceInfo(componentName, 0); + } catch (NameNotFoundException ex) { + Slog.e(TAG, + "Trace reporting service " + componentName.toShortString() + " does not exist"); + return false; + } + if (!Manifest.permission.BIND_TRACE_REPORT_SERVICE.equals(info.permission)) { + Slog.e(TAG, + "Trace reporting service " + componentName.toShortString() + + " does not request " + Manifest.permission.BIND_TRACE_REPORT_SERVICE + + " permission; instead requests " + info.permission); + return false; + } + return true; + } } diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 9bed24d05f3d..52f7d101ce99 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -122,7 +122,8 @@ public class TrustManagerService extends SystemService { private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13; private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14; private static final int MSG_SCHEDULE_TRUST_TIMEOUT = 15; - public static final int MSG_USER_REQUESTED_UNLOCK = 16; + private static final int MSG_USER_REQUESTED_UNLOCK = 16; + private static final int MSG_ENABLE_TRUST_AGENT = 17; private static final String REFRESH_DEVICE_LOCKED_EXCEPT_USER = "except"; @@ -631,6 +632,24 @@ public class TrustManagerService extends SystemService { } } + + /** + * Uses {@link LockPatternUtils} to enable the setting for trust agent in the specified + * component name. This should only be used for testing. + */ + private void enableTrustAgentForUserForTest(@NonNull ComponentName componentName, int userId) { + Log.i(TAG, + "Enabling trust agent " + componentName.flattenToString() + " for user " + userId); + List<ComponentName> agents = + new ArrayList<>(mLockPatternUtils.getEnabledTrustAgents(userId)); + if (!agents.contains(componentName)) { + agents.add(componentName); + } + // Even if the agent was already there, we still call setEnabledTrustAgents to trigger a + // refresh of installed agents. + mLockPatternUtils.setEnabledTrustAgents(agents, userId); + } + boolean isDeviceLockedInner(int userId) { synchronized (mDeviceLockedForUser) { return mDeviceLockedForUser.get(userId, true); @@ -929,6 +948,7 @@ public class TrustManagerService extends SystemService { continue; } allowedAgents.add(resolveInfo); + if (DEBUG) Slog.d(TAG, "Adding agent " + getComponentName(resolveInfo)); } return allowedAgents; } @@ -1158,6 +1178,13 @@ public class TrustManagerService extends SystemService { } @Override + public void enableTrustAgentForUserForTest(ComponentName componentName, int userId) + throws RemoteException { + enforceReportPermission(); + mHandler.obtainMessage(MSG_ENABLE_TRUST_AGENT, userId, 0, componentName).sendToTarget(); + } + + @Override public void reportKeyguardShowingChanged() throws RemoteException { enforceReportPermission(); // coalesce refresh messages. @@ -1433,6 +1460,9 @@ public class TrustManagerService extends SystemService { // This is also called when the security mode of a user changes. refreshDeviceLockedForUser(UserHandle.USER_ALL); break; + case MSG_ENABLE_TRUST_AGENT: + enableTrustAgentForUserForTest((ComponentName) msg.obj, msg.arg1); + break; case MSG_KEYGUARD_SHOWING_CHANGED: refreshDeviceLockedForUser(mCurrentUser); break; diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 7164c6c601ef..ff96aebba708 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -43,6 +43,7 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_N import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH; import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller; +import android.annotation.ColorInt; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; @@ -1081,7 +1082,7 @@ class ActivityClientController extends IActivityClientController.Stub { @Override public void overridePendingTransition(IBinder token, String packageName, - int enterAnim, int exitAnim) { + int enterAnim, int exitAnim, @ColorInt int backgroundColor) { final long origId = Binder.clearCallingIdentity(); synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); @@ -1091,7 +1092,7 @@ class ActivityClientController extends IActivityClientController.Stub { r.mOverrideTaskTransition); r.mTransitionController.setOverrideAnimation( TransitionInfo.AnimationOptions.makeCustomAnimOptions(packageName, - enterAnim, exitAnim, r.mOverrideTaskTransition), + enterAnim, exitAnim, backgroundColor, r.mOverrideTaskTransition), null /* startCallback */, null /* finishCallback */); } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c0eee6136f34..3fe616660284 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -716,6 +716,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 @@ -4469,6 +4474,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pendingOptions.getOverrideTaskTransition()); options = AnimationOptions.makeCustomAnimOptions(pendingOptions.getPackageName(), pendingOptions.getCustomEnterResId(), pendingOptions.getCustomExitResId(), + pendingOptions.getCustomBackgroundColor(), pendingOptions.getOverrideTaskTransition()); startCallback = pendingOptions.getAnimationStartedListener(); finishCallback = pendingOptions.getAnimationFinishedListener(); @@ -6928,7 +6934,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) { @@ -7626,6 +7632,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 @@ -7687,6 +7711,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. @@ -7726,8 +7751,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/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/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/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index a049d6547396..d4a7a5d68929 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -380,8 +380,11 @@ class RecentTasks { final ComponentName cn = ComponentName.unflattenFromString(rawRecentsComponent); if (cn != null) { try { - final ApplicationInfo appInfo = AppGlobals.getPackageManager() - .getApplicationInfo(cn.getPackageName(), 0, mService.mContext.getUserId()); + final ApplicationInfo appInfo = AppGlobals.getPackageManager().getApplicationInfo( + cn.getPackageName(), + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS, + mService.mContext.getUserId()); if (appInfo != null) { mRecentsUid = appInfo.uid; mRecentsComponent = cn; 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/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/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/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_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index 0da8f7ef0dea..161d7ce350fc 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -1504,11 +1504,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."); } diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp index 5178132e4a2e..f8a81682bdcf 100644 --- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp +++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp @@ -16,20 +16,18 @@ #define LOG_TAG "NetworkStatsNative" +#include <cutils/qtaguid.h> #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <inttypes.h> -#include <sys/stat.h> -#include <sys/types.h> - -#include "core_jni_helpers.h" #include <jni.h> #include <nativehelper/ScopedUtfChars.h> -#include <utils/misc.h> +#include <sys/stat.h> +#include <sys/types.h> #include <utils/Log.h> +#include <utils/misc.h> -#include "android-base/unique_fd.h" #include "bpf/BpfUtils.h" #include "netdbpf/BpfNetworkStats.h" @@ -104,10 +102,15 @@ static jlong getUidStat(JNIEnv* env, jclass clazz, jint uid, jint type) { } } +static int deleteTagData(JNIEnv* /* env */, jclass /* clazz */, jint uid) { + return qtaguid_deleteTagData(0, uid); +} + static const JNINativeMethod gMethods[] = { {"nativeGetTotalStat", "(I)J", (void*)getTotalStat}, {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*)getIfaceStat}, {"nativeGetUidStat", "(II)J", (void*)getUidStat}, + {"nativeDeleteTagData", "(I)I", (void*)deleteTagData}, }; int register_android_server_net_NetworkStatsService(JNIEnv* env) { 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/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp index fbdeec6b897e..6c0d5d984980 100644 --- a/services/core/jni/gnss/GnssMeasurementCallback.cpp +++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp @@ -359,9 +359,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); @@ -508,8 +506,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,10 +516,7 @@ 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, 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/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 574dbfd1046d..79d80366efd3 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -38,6 +38,10 @@ <xs:annotation name="nonnull"/> <xs:annotation name="final"/> </xs:element> + <xs:element type="thermalThrottling" name="thermalThrottling"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0" maxOccurs="1"/> <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" /> @@ -154,6 +158,37 @@ </xs:restriction> </xs:simpleType> + <xs:complexType name="thermalThrottling"> + <xs:complexType> + <xs:element type="brightnessThrottlingMap" name="brightnessThrottlingMap"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:complexType> + </xs:complexType> + + <xs:complexType name="brightnessThrottlingMap"> + <xs:sequence> + <xs:element name="brightnessThrottlingPoint" type="brightnessThrottlingPoint" maxOccurs="unbounded" minOccurs="1"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="brightnessThrottlingPoint"> + <xs:sequence> + <xs:element type="thermalStatus" name="thermalStatus"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + <xs:element type="nonNegativeDecimal" name="brightness"> + <xs:annotation name="nonnull"/> + <xs:annotation name="final"/> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="nitsMap"> <xs:sequence> <xs:element name="point" type="point" maxOccurs="unbounded" minOccurs="2"> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 04f0916dbc54..0b7df4d0bc7c 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -7,6 +7,19 @@ package com.android.server.display.config { method public final void setMinimum(@NonNull java.math.BigDecimal); } + public class BrightnessThrottlingMap { + ctor public BrightnessThrottlingMap(); + method @NonNull public final java.util.List<com.android.server.display.config.BrightnessThrottlingPoint> getBrightnessThrottlingPoint(); + } + + public class BrightnessThrottlingPoint { + ctor public BrightnessThrottlingPoint(); + method @NonNull public final java.math.BigDecimal getBrightness(); + method @NonNull public final com.android.server.display.config.ThermalStatus getThermalStatus(); + method public final void setBrightness(@NonNull java.math.BigDecimal); + method public final void setThermalStatus(@NonNull com.android.server.display.config.ThermalStatus); + } + public class Density { ctor public Density(); method @NonNull public final java.math.BigInteger getDensity(); @@ -39,6 +52,7 @@ package com.android.server.display.config { method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease(); + method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling(); method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds); method public final void setAmbientLightHorizonLong(java.math.BigInteger); method public final void setAmbientLightHorizonShort(java.math.BigInteger); @@ -54,6 +68,7 @@ package com.android.server.display.config { method public final void setScreenBrightnessRampFastIncrease(java.math.BigDecimal); method public final void setScreenBrightnessRampSlowDecrease(java.math.BigDecimal); method public final void setScreenBrightnessRampSlowIncrease(java.math.BigDecimal); + method public final void setThermalThrottling(@NonNull com.android.server.display.config.ThermalThrottling); } public class DisplayQuirks { @@ -131,6 +146,12 @@ package com.android.server.display.config { enum_constant public static final com.android.server.display.config.ThermalStatus shutdown; } + public class ThermalThrottling { + ctor public ThermalThrottling(); + method @NonNull public final com.android.server.display.config.BrightnessThrottlingMap getBrightnessThrottlingMap(); + method public final void setBrightnessThrottlingMap(@NonNull com.android.server.display.config.BrightnessThrottlingMap); + } + public class Thresholds { ctor public Thresholds(); method @NonNull public final com.android.server.display.config.BrightnessThresholds getBrighteningThresholds(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 733cfcdfaea5..3bda7bf049c4 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), @@ -18141,27 +18262,51 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @DeviceOwnerType int deviceOwnerType) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); - verifyDeviceOwnerTypePreconditions(admin); - final String packageName = admin.getPackageName(); + synchronized (getLockObject()) { + setDeviceOwnerTypeLocked(admin, deviceOwnerType); + } + } + + private void setDeviceOwnerTypeLocked(ComponentName admin, + @DeviceOwnerType int deviceOwnerType) { + String packageName = admin.getPackageName(); + + verifyDeviceOwnerTypePreconditionsLocked(admin); Preconditions.checkState(!mOwners.isDeviceOwnerTypeSetForDeviceOwner(packageName), "The device owner type has already been set for " + packageName); - synchronized (getLockObject()) { - mOwners.setDeviceOwnerType(packageName, deviceOwnerType); - } + mOwners.setDeviceOwnerType(packageName, deviceOwnerType); } @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 +18317,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 +18358,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 +18399,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 +18429,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,7 +18451,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public List<String> getSsidAllowlist() { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) || isSystemUid(caller), "SSID allowlist can only be retrieved by a device owner or " + "a profile owner on an organization-owned device or a system app."); @@ -18322,7 +18467,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,7 +18489,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public List<String> getSsidDenylist() { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) || isSystemUid(caller), "SSID denylist can only be retrieved by a device owner or " + "a profile owner on an organization-owned device or a system app."); 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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 74e04ed6e58b..3fd5cd167d95 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -213,8 +213,6 @@ import com.android.server.wm.WindowManagerService; import dalvik.system.VMRuntime; -import com.google.android.startop.iorap.IorapForwardingService; - import java.io.File; import java.io.FileDescriptor; import java.io.IOException; @@ -265,8 +263,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 +273,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 = @@ -1621,10 +1615,6 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(PinnerService.class); t.traceEnd(); - t.traceBegin("IorapForwardingService"); - mSystemServiceManager.startService(IorapForwardingService.class); - t.traceEnd(); - if (Build.IS_DEBUGGABLE && ProfcollectForwardingService.enabled()) { t.traceBegin("ProfcollectForwardingService"); mSystemServiceManager.startService(ProfcollectForwardingService.class); @@ -2000,16 +1990,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/startop/Android.bp b/services/startop/Android.bp deleted file mode 100644 index c56c463d0168..000000000000 --- a/services/startop/Android.bp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - // SPDX-license-identifier-MIT - // SPDX-license-identifier-Unicode-DFS - default_applicable_licenses: ["frameworks_base_license"], -} - -java_library_static { - name: "services.startop", - defaults: ["platform_service_defaults"], - - static_libs: [ - // frameworks/base/startop/iorap - "services.startop.iorap", - ], -} diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index e40f5439d0c2..e1aa08d9176a 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.MATCH_ANY_USER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.returnsArgAt; import static org.mockito.ArgumentMatchers.any; @@ -40,6 +41,7 @@ import android.app.IActivityManager; import android.app.usage.UsageEvents.Event; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; +import android.apphibernation.HibernationStats; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -68,6 +70,8 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; /** @@ -126,7 +130,7 @@ public final class AppHibernationServiceTest { mUsageEventListener = mUsageEventListenerCaptor.getValue(); doReturn(mUserInfos).when(mUserManager).getUsers(); - + doReturn(true).when(mPackageManagerInternal).canQueryPackage(anyInt(), any()); doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any(), any()); @@ -376,6 +380,58 @@ public final class AppHibernationServiceTest { assertFalse(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1)); } + @Test + public void testGetHibernationStatsForUser_getsStatsForPackage() { + // GIVEN a package is hibernating globally and for a user + mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); + mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); + + // WHEN we ask for the hibernation stats for the package + Map<String, HibernationStats> stats = + mAppHibernationService.getHibernationStatsForUser( + Set.of(PACKAGE_NAME_1), USER_ID_1); + + // THEN the stats exist for the package + assertTrue(stats.containsKey(PACKAGE_NAME_1)); + } + + @Test + public void testGetHibernationStatsForUser_noExceptionThrownWhenPackageDoesntExist() { + // WHEN we ask for the hibernation stats for a package that doesn't exist + Map<String, HibernationStats> stats = + mAppHibernationService.getHibernationStatsForUser( + Set.of(PACKAGE_NAME_1), USER_ID_1); + + // THEN no exception is thrown and empty stats are returned + assertNotNull(stats); + } + + @Test + public void testGetHibernationStatsForUser_returnsAllIfNoPackagesSpecified() + throws RemoteException { + // GIVEN an unlocked user with all packages installed and they're all hibernating + UserInfo userInfo = + addUser(USER_ID_2, new String[]{PACKAGE_NAME_1, PACKAGE_NAME_2, PACKAGE_NAME_3}); + doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); + mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); + mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); + mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_2, true); + mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_2, true); + mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_3, true); + mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_3, USER_ID_2, true); + + // WHEN we ask for the hibernation stats with no package specified + Map<String, HibernationStats> stats = + mAppHibernationService.getHibernationStatsForUser( + null /* packageNames */, USER_ID_2); + + // THEN all the package stats are returned + assertTrue(stats.containsKey(PACKAGE_NAME_1)); + assertTrue(stats.containsKey(PACKAGE_NAME_2)); + assertTrue(stats.containsKey(PACKAGE_NAME_3)); + } + /** * Mock a usage event occurring. * 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..ec884f59014f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.same; +import static org.mockito.Mockito.verify; + +import android.hardware.biometrics.IBiometricContextListener; +import android.hardware.biometrics.common.OperationContext; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +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; + + private OperationContext mOpContext = new OperationContext(); + private IBiometricContextListener mListener; + private BiometricContextProvider mProvider; + + @Before + public void setup() throws RemoteException { + mProvider = new BiometricContextProvider(mStatusBarService, null /* handler */); + ArgumentCaptor<IBiometricContextListener> captor = + ArgumentCaptor.forClass(IBiometricContextListener.class); + verify(mStatusBarService).setBiometicContextListener(captor.capture()); + mListener = captor.getValue(); + } + + @Test + public void testIsAoD() throws RemoteException { + mListener.onDozeChanged(true); + assertThat(mProvider.isAoD()).isTrue(); + 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)); + } +} 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..b0eb810b5da9 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 @@ -44,7 +44,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 +56,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 @@ -68,8 +72,6 @@ public class BiometricLoggerTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); - mContext.addMockSystemService(SensorManager.class, mSensorManager); when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn( new Sensor(new InputSensorInfo("", "", 0, 0, Sensor.TYPE_LIGHT, 0, 0, 0, 0, 0, 0, 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..25585dd0cfc3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.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.server.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +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.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.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; + private static final boolean HAS_AOD = true; + + @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.isAoD()).thenReturn(HAS_AOD); + } + + @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); + + verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture()); + verify(mHal, never()).authenticate(anyLong()); + + final OperationContext opContext = mOperationContextCaptor.getValue(); + assertThat(opContext.isAoD).isEqualTo(HAS_AOD); + } + + 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..6c72ebfd3674 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +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.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceDetectClientTest { + + private static final int USER_ID = 12; + private static final boolean HAS_AOD = true; + + @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.isAoD()).thenReturn(HAS_AOD); + } + + @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); + + verify(mHal).detectInteractionWithContext(mOperationContextCaptor.capture()); + verify(mHal, never()).detectInteraction(); + + final OperationContext opContext = mOperationContextCaptor.getValue(); + assertThat(opContext.isAoD).isEqualTo(HAS_AOD); + } + + 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..22070e9579ee --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.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.server.biometrics.sensors.face.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyByte; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +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.Rule; +import org.junit.Test; +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; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @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); + + verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), any()); + 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/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/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..5c360e9147c1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.never; +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.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 boolean HAS_AOD = true; + 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; + + @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.isAoD()).thenReturn(HAS_AOD); + } + + @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); + + verify(mHal).authenticateWithContext(eq(OP_ID), mOperationContextCaptor.capture()); + verify(mHal, never()).authenticate(anyLong()); + + final OperationContext opContext = mOperationContextCaptor.getValue(); + assertThat(opContext.isAoD).isEqualTo(HAS_AOD); + } + + @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 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..295fe4766a85 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.Mockito.any; +import static org.mockito.Mockito.never; +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.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintDetectClientTest { + + private static final int USER_ID = 8; + private static final boolean HAS_AOD = true; + + @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.isAoD()).thenReturn(HAS_AOD); + } + + @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); + + verify(mHal).detectInteractionWithContext(mOperationContextCaptor.capture()); + 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..7e44eab0a556 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static com.google.common.truth.Truth.assertThat; + +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.never; +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.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +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; + @Captor + private ArgumentCaptor<OperationContext> mOperationContextCaptor; + @Captor + private ArgumentCaptor<PointerContext> mPointerContextCaptor; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @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); + + verify(mHal).enrollWithContext(any(), mOperationContextCaptor.capture()); + 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 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/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/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..ceb723a407f9 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()); @@ -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..64ce6b282f17 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; @@ -7764,6 +7776,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/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/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java new file mode 100644 index 000000000000..0ed90d27db99 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.display; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.display.BrightnessInfo; +import android.os.Handler; +import android.os.IThermalEventListener; +import android.os.IThermalService; +import android.os.Message; +import android.os.PowerManager; +import android.os.Temperature.ThrottlingStatus; +import android.os.Temperature; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.display.BrightnessThrottler.Injector; +import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData; + +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 java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class BrightnessThrottlerTest { + private static final float EPSILON = 0.000001f; + + private Handler mHandler; + private TestLooper mTestLooper; + + @Mock IThermalService mThermalServiceMock; + @Mock Injector mInjectorMock; + + @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mInjectorMock.getThermalService()).thenReturn(mThermalServiceMock); + mTestLooper = new TestLooper(); + mHandler = new Handler(mTestLooper.getLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + return true; + } + }); + + } + + ///////////////// + // Test Methods + ///////////////// + + @Test + public void testBrightnessThrottlingData() { + List<ThrottlingLevel> singleLevel = new ArrayList<>(); + singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f)); + + List<ThrottlingLevel> validLevels = new ArrayList<>(); + validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f)); + validLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f)); + + List<ThrottlingLevel> unsortedThermalLevels = new ArrayList<>(); + unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f)); + unsortedThermalLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f)); + + List<ThrottlingLevel> unsortedBrightnessLevels = new ArrayList<>(); + unsortedBrightnessLevels.add( + new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.25f)); + unsortedBrightnessLevels.add( + new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.62f)); + + List<ThrottlingLevel> unsortedLevels = new ArrayList<>(); + unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f)); + unsortedLevels.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f)); + + List<ThrottlingLevel> invalidLevel = new ArrayList<>(); + invalidLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + PowerManager.BRIGHTNESS_MAX + EPSILON)); + + // Test invalid data + BrightnessThrottlingData data; + data = BrightnessThrottlingData.create((List<ThrottlingLevel>)null); + assertEquals(data, null); + data = BrightnessThrottlingData.create((BrightnessThrottlingData)null); + assertEquals(data, null); + data = BrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>()); + assertEquals(data, null); + data = BrightnessThrottlingData.create(unsortedThermalLevels); + assertEquals(data, null); + data = BrightnessThrottlingData.create(unsortedBrightnessLevels); + assertEquals(data, null); + data = BrightnessThrottlingData.create(unsortedLevels); + assertEquals(data, null); + data = BrightnessThrottlingData.create(invalidLevel); + assertEquals(data, null); + + // Test valid data + data = BrightnessThrottlingData.create(singleLevel); + assertNotEquals(data, null); + assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels); + + data = BrightnessThrottlingData.create(validLevels); + assertNotEquals(data, null); + assertThrottlingLevelsEquals(validLevels, data.throttlingLevels); + } + + @Test + public void testThrottlingUnsupported() throws Exception { + final BrightnessThrottler throttler = createThrottlerUnsupported(); + assertFalse(throttler.deviceSupportsThrottling()); + + // Thermal listener shouldn't be registered if throttling is unsupported + verify(mInjectorMock, never()).getThermalService(); + + // Ensure that brightness is uncapped when the device doesn't support throttling + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + } + + @Test + public void testThrottlingSingleLevel() throws Exception { + final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + 0.25f); + + List<ThrottlingLevel> levels = new ArrayList<>(); + levels.add(level); + final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + final BrightnessThrottler throttler = createThrottlerSupported(data); + assertTrue(throttler.deviceSupportsThrottling()); + + verify(mThermalServiceMock).registerThermalEventListenerWithType( + mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN)); + final IThermalEventListener listener = mThermalEventListenerCaptor.getValue(); + + // Set status too low to trigger throttling + listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason()); + + // Set status just high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(level.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Set status more than high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1)); + mTestLooper.dispatchAll(); + assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Return to the lower throttling level + listener.notifyThrottling(getSkinTemp(level.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Cool down + listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, + throttler.getBrightnessMaxReason()); + } + + @Test + public void testThrottlingMultiLevel() throws Exception { + final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, + 0.62f); + final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + 0.25f); + + List<ThrottlingLevel> levels = new ArrayList<>(); + levels.add(levelLo); + levels.add(levelHi); + final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + final BrightnessThrottler throttler = createThrottlerSupported(data); + assertTrue(throttler.deviceSupportsThrottling()); + + verify(mThermalServiceMock).registerThermalEventListenerWithType( + mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN)); + final IThermalEventListener listener = mThermalEventListenerCaptor.getValue(); + + // Set status too low to trigger throttling + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason()); + + // Set status just high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Set status to an intermediate throttling level + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1)); + mTestLooper.dispatchAll(); + assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Set status to the highest configured throttling level + listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Set status to exceed the highest configured throttling level + listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1)); + mTestLooper.dispatchAll(); + assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Return to an intermediate throttling level + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1)); + mTestLooper.dispatchAll(); + assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Return to the lowest configured throttling level + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, + throttler.getBrightnessMaxReason()); + + // Cool down + listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason()); + } + + private void assertThrottlingLevelsEquals( + List<ThrottlingLevel> expected, + List<ThrottlingLevel> actual) { + assertEquals(expected.size(), actual.size()); + + for (int i = 0; i < expected.size(); i++) { + ThrottlingLevel expectedLevel = expected.get(i); + ThrottlingLevel actualLevel = actual.get(i); + + assertEquals(expectedLevel.thermalStatus, actualLevel.thermalStatus); + assertEquals(expectedLevel.brightness, actualLevel.brightness, 0.0f); + } + } + + private BrightnessThrottler createThrottlerUnsupported() { + return new BrightnessThrottler(mInjectorMock, mHandler, null, () -> {}); + } + + private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) { + assertNotNull(data); + return new BrightnessThrottler(mInjectorMock, mHandler, data, () -> {}); + } + + private Temperature getSkinTemp(@ThrottlingStatus int status) { + return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 418831f47c1a..40c039210939 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -1461,7 +1461,8 @@ public class DisplayModeDirectorTest { // Turn on HBM, with brightness in the HBM range when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, hbmRefreshRate); @@ -1469,7 +1470,8 @@ public class DisplayModeDirectorTest { // Turn on HBM, with brightness below the HBM range when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1477,7 +1479,8 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1485,7 +1488,8 @@ public class DisplayModeDirectorTest { // Turn on HBM, with brightness in the HBM range when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, hbmRefreshRate); @@ -1493,7 +1497,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1501,7 +1505,8 @@ public class DisplayModeDirectorTest { // Turn on HBM, with brightness below the HBM range when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1509,7 +1514,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1580,7 +1585,7 @@ public class DisplayModeDirectorTest { // Turn on HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, initialRefreshRate); @@ -1598,7 +1603,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1606,7 +1611,7 @@ public class DisplayModeDirectorTest { // Turn HBM on again and ensure the updated vote value stuck when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, updatedRefreshRate); @@ -1622,7 +1627,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1654,7 +1659,7 @@ public class DisplayModeDirectorTest { // Turn on HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1662,7 +1667,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1694,7 +1699,7 @@ public class DisplayModeDirectorTest { // Turn on HBM when HBM is supported; expect a valid transition point and a vote. when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, 60.0f); @@ -1702,7 +1707,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1711,7 +1716,7 @@ public class DisplayModeDirectorTest { // no vote. when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - HBM_TRANSITION_POINT_INVALID)); + HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1720,7 +1725,7 @@ public class DisplayModeDirectorTest { // no vote. when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, - HBM_TRANSITION_POINT_INVALID)); + HBM_TRANSITION_POINT_INVALID, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1728,7 +1733,7 @@ public class DisplayModeDirectorTest { // Turn off HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertNull(vote); @@ -1737,7 +1742,8 @@ public class DisplayModeDirectorTest { private void setHbmAndAssertRefreshRate( DisplayModeDirector director, DisplayListener listener, int mode, float rr) { when(mInjector.getBrightnessInfo(DISPLAY_ID)) - .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT)); + .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); @@ -1817,7 +1823,7 @@ public class DisplayModeDirectorTest { // Turn on HBM when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, - TRANSITION_POINT)); + TRANSITION_POINT, BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE); assertVoteForRefreshRate(vote, 60.f); @@ -1978,7 +1984,8 @@ public class DisplayModeDirectorTest { when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn( new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f, - BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT)); + BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT, + BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE)); listener.onDisplayChanged(DISPLAY_ID); } 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 4bb5d7482e25..6203c2f54f07 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -16,6 +16,8 @@ package com.android.server.display; +import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; +import static android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; @@ -201,7 +203,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); // Verify we are in HBM assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); @@ -233,7 +235,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); // Verify we are in HBM assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); @@ -258,18 +260,18 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2); // Verify we are in HBM assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); - hbmc.onBrightnessChanged(TRANSITION_POINT - 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT - 0.01f); advanceTime(1); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); @@ -288,13 +290,13 @@ public class HighBrightnessModeControllerTest { hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); // Go into HBM for half the allowed window - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); // Move lux below threshold (ending first event); hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); - hbmc.onBrightnessChanged(TRANSITION_POINT); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT); assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF); // Move up some amount of time so that there's still time in the window even after a @@ -304,7 +306,7 @@ public class HighBrightnessModeControllerTest { // Go into HBM for just under the second half of allowed window hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 1); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 1); advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT); @@ -434,7 +436,7 @@ public class HighBrightnessModeControllerTest { float brightness = 0.5f; float expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT, DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range - hbmc.onBrightnessChanged(brightness); + hbmcOnBrightnessChanged(hbmc, brightness); advanceTime(0); assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON); @@ -442,21 +444,21 @@ public class HighBrightnessModeControllerTest { brightness = 0.33f; expectedHdrBrightness = MathUtils.map(DEFAULT_MIN, TRANSITION_POINT, DEFAULT_MIN, DEFAULT_MAX, brightness); // map value from normal range to hdr range - hbmc.onBrightnessChanged(brightness); + hbmcOnBrightnessChanged(hbmc, brightness); advanceTime(0); assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON); // Try the min value brightness = DEFAULT_MIN; expectedHdrBrightness = DEFAULT_MIN; - hbmc.onBrightnessChanged(brightness); + hbmcOnBrightnessChanged(hbmc, brightness); advanceTime(0); assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON); // Try the max value brightness = TRANSITION_POINT; expectedHdrBrightness = DEFAULT_MAX; - hbmc.onBrightnessChanged(brightness); + hbmcOnBrightnessChanged(hbmc, brightness); advanceTime(0); assertEquals(expectedHdrBrightness, hbmc.getHdrBrightnessValue(), EPSILON); } @@ -467,7 +469,7 @@ public class HighBrightnessModeControllerTest { final int displayStatsId = mDisplayUniqueId.hashCode(); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); - hbmc.onBrightnessChanged(TRANSITION_POINT); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT); hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/); advanceTime(0); @@ -489,7 +491,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); // Verify Stats HBM_ON_SUNLIGHT verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), @@ -506,12 +508,12 @@ public class HighBrightnessModeControllerTest { } @Test - public void tetHbmStats_NbmHdrNoReport() { + public void testHbmStats_NbmHdrNoReport() { final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); final int displayStatsId = mDisplayUniqueId.hashCode(); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); - hbmc.onBrightnessChanged(DEFAULT_MIN); + hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN); hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/); advanceTime(0); @@ -524,7 +526,27 @@ public class HighBrightnessModeControllerTest { } @Test - public void testHbmStats_ThermalOff() throws Exception { + public void testHbmStats_HighLuxLowBrightnessNoReport() { + final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); + final int displayStatsId = mDisplayUniqueId.hashCode(); + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN); + advanceTime(0); + // verify in HBM sunlight mode + assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); + + // Verify Stats HBM_ON_SUNLIGHT not report + verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + anyInt()); + } + + // Test reporting of thermal throttling when triggered by HighBrightnessModeController's + // internal thermal throttling. + @Test + public void testHbmStats_InternalThermalOff() throws Exception { final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); final int displayStatsId = mDisplayUniqueId.hashCode(); @@ -534,7 +556,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(1); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), @@ -548,6 +570,37 @@ public class HighBrightnessModeControllerTest { eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT)); } + // Test reporting of thermal throttling when triggered externally through + // HighBrightnessModeController.onBrightnessChanged() + @Test + public void testHbmStats_ExternalThermalOff() throws Exception { + final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); + final int displayStatsId = mDisplayUniqueId.hashCode(); + final float hbmBrightness = TRANSITION_POINT + 0.01f; + final float nbmBrightness = TRANSITION_POINT - 0.01f; + + hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); + hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); + // Brightness is unthrottled, HBM brightness granted + hbmc.onBrightnessChanged(hbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_NONE); + advanceTime(1); + 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)); + + // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted) + hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness, BRIGHTNESS_MAX_REASON_THERMAL); + advanceTime(1); + // We expect HBM mode to remain set to sunlight, indicating that HBMC *allows* this mode. + // However, we expect the HBM state reported by HBMC to be off, since external thermal + // throttling (reported to HBMC through onBrightnessChanged()) lowers brightness to below + // the HBM transition point. + assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); + verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT)); + } + @Test public void testHbmStats_TimeOut() { final HighBrightnessModeController hbmc = createDefaultHbm(new OffsettableClock()); @@ -555,7 +608,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), @@ -576,7 +629,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), @@ -595,7 +648,7 @@ public class HighBrightnessModeControllerTest { hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); - hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f); + hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), @@ -610,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); @@ -649,4 +726,8 @@ public class HighBrightnessModeControllerTest { private Temperature getSkinTemp(@ThrottlingStatus int status) { return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status); } + + private void hbmcOnBrightnessChanged(HighBrightnessModeController hbmc, float brightness) { + hbmc.onBrightnessChanged(brightness, brightness, BRIGHTNESS_MAX_REASON_NONE); + } } diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index e80721a10e90..94cf20f9c15b 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -51,10 +51,9 @@ import static android.net.NetworkPolicyManager.uidRulesToString; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.net.NetworkStats.METERED_NO; import static android.net.NetworkStats.METERED_YES; +import static android.net.NetworkTemplate.MATCH_CARRIER; import static android.net.NetworkTemplate.MATCH_MOBILE; -import static android.net.NetworkTemplate.buildTemplateCarrierMetered; -import static android.net.NetworkTemplate.buildTemplateWifi; -import static android.net.TrafficStats.MB_IN_BYTES; +import static android.net.NetworkTemplate.MATCH_WIFI; import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED; import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT; @@ -205,6 +204,7 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TimeZone; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -229,10 +229,12 @@ public class NetworkPolicyManagerServiceTest { private static final int TEST_SUB_ID = 42; private static final Network TEST_NETWORK = mock(Network.class, CALLS_REAL_METHODS); - - private static NetworkTemplate sTemplateWifi = buildTemplateWifi(TEST_WIFI_NETWORK_KEY); + private static NetworkTemplate sTemplateWifi = new NetworkTemplate.Builder(MATCH_WIFI) + .setWifiNetworkKeys(Set.of(TEST_WIFI_NETWORK_KEY)).build(); private static NetworkTemplate sTemplateCarrierMetered = - buildTemplateCarrierMetered(TEST_IMSI); + new NetworkTemplate.Builder(MATCH_CARRIER) + .setSubscriberIds(Set.of(TEST_IMSI)) + .setMeteredness(METERED_YES).build(); /** * Path on assets where files used by {@link NetPolicyXml} are located. @@ -1160,11 +1162,12 @@ public class NetworkPolicyManagerServiceTest { mPolicyListener.expect().onMeteredIfacesChanged(any()); setNetworkPolicies(new NetworkPolicy( - sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, 1 * MB_IN_BYTES, 2 * MB_IN_BYTES, false)); + sTemplateWifi, CYCLE_DAY, TIMEZONE_UTC, DataUnit.MEBIBYTES.toBytes(1), + DataUnit.MEBIBYTES.toBytes(2), false)); mPolicyListener.waitAndVerify().onMeteredIfacesChanged(eq(new String[]{TEST_IFACE})); verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE, - (2 * MB_IN_BYTES) - 512); + DataUnit.MEBIBYTES.toBytes(2) - 512); } @Test @@ -1252,7 +1255,7 @@ public class NetworkPolicyManagerServiceTest { reset(mTelephonyManager, mNetworkManager, mNotifManager); TelephonyManager tmSub = expectMobileDefaults(); - mService.snoozeLimit(NetworkTemplate.buildTemplateCarrierMetered(TEST_IMSI)); + mService.snoozeLimit(sTemplateCarrierMetered); mService.updateNetworks(); verify(tmSub, atLeastOnce()).setPolicyDataEnabled(true); @@ -1955,7 +1958,7 @@ public class NetworkPolicyManagerServiceTest { assertEquals("Unexpected number of network policies", 1, policies.length); NetworkPolicy actualPolicy = policies[0]; assertEquals("Unexpected template match rule in network policies", - NetworkTemplate.MATCH_WIFI, + MATCH_WIFI, actualPolicy.template.getMatchRule()); assertEquals("Unexpected subscriberIds size in network policies", actualPolicy.template.getSubscriberIds().size(), 0); @@ -2026,7 +2029,10 @@ public class NetworkPolicyManagerServiceTest { private static NetworkPolicy buildFakeCarrierPolicy(int cycleDay, long warningBytes, long limitBytes, boolean inferred) { - final NetworkTemplate template = buildTemplateCarrierMetered(FAKE_SUBSCRIBER_ID); + // TODO: Refactor this to use sTemplateCarrierMetered. + final NetworkTemplate template = new NetworkTemplate.Builder(MATCH_CARRIER) + .setSubscriberIds(Set.of(FAKE_SUBSCRIBER_ID)) + .setMeteredness(METERED_YES).build(); return new NetworkPolicy(template, cycleDay, TimeZone.getDefault().getID(), warningBytes, limitBytes, SNOOZE_NEVER, SNOOZE_NEVER, true, inferred); } 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/LowPowerStandbyControllerTest.java b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java new file mode 100644 index 000000000000..4ae9613a36d3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/LowPowerStandbyControllerTest.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +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.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AlarmManager; +import android.content.Intent; +import android.content.res.Resources; +import android.os.IPowerManager; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.test.TestLooper; +import android.provider.Settings; +import android.test.mock.MockContentResolver; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.LocalServices; +import com.android.server.testutils.OffsettableClock; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link com.android.server.power.LowPowerStandbyController}. + * + * Build/Install/Run: + * atest LowPowerStandbyControllerTest + */ +public class LowPowerStandbyControllerTest { + private static final int STANDBY_TIMEOUT = 5000; + + private LowPowerStandbyController mController; + private BroadcastInterceptingContext mContextSpy; + private Resources mResourcesSpy; + private OffsettableClock mClock; + private TestLooper mTestLooper; + + @Mock + private AlarmManager mAlarmManagerMock; + @Mock + private IPowerManager mIPowerManagerMock; + @Mock + private PowerManagerInternal mPowerManagerInternalMock; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContextSpy = spy(new BroadcastInterceptingContext(InstrumentationRegistry.getContext())); + when(mContextSpy.getSystemService(AlarmManager.class)).thenReturn(mAlarmManagerMock); + PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock, null, null); + when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(powerManager); + addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock); + + when(mIPowerManagerMock.isInteractive()).thenReturn(true); + + mResourcesSpy = spy(mContextSpy.getResources()); + when(mContextSpy.getResources()).thenReturn(mResourcesSpy); + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_lowPowerStandbySupported)) + .thenReturn(true); + when(mResourcesSpy.getInteger( + com.android.internal.R.integer.config_lowPowerStandbyNonInteractiveTimeout)) + .thenReturn(STANDBY_TIMEOUT); + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_lowPowerStandbyEnabledByDefault)) + .thenReturn(false); + + FakeSettingsProvider.clearSettingsProvider(); + MockContentResolver cr = new MockContentResolver(mContextSpy); + cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mContextSpy.getContentResolver()).thenReturn(cr); + + mClock = new OffsettableClock.Stopped(); + mTestLooper = new TestLooper(mClock::now); + + mController = new LowPowerStandbyController(mContextSpy, mTestLooper.getLooper(), + () -> mClock.now()); + } + + @After + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(PowerManagerInternal.class); + LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class); + } + + @Test + public void testOnSystemReady_isInactivate() { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + + assertThat(mController.isActive()).isFalse(); + verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); + } + + @Test + public void testActivate() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + mController.setEnabled(true); + setNonInteractive(); + setDeviceIdleMode(true); + awaitStandbyTimeoutAlarm(); + assertThat(mController.isActive()).isTrue(); + verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true); + } + + private void awaitStandbyTimeoutAlarm() { + ArgumentCaptor<Long> timeArg = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor<AlarmManager.OnAlarmListener> listenerArg = + ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class); + verify(mAlarmManagerMock).setExact( + eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), + timeArg.capture(), anyString(), + listenerArg.capture(), any()); + mClock.reset(); + mClock.fastForward(timeArg.getValue()); + listenerArg.getValue().onAlarm(); + mTestLooper.dispatchAll(); + } + + @Test + public void testOnNonInteractive_notImmediatelyActive() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + mController.setEnabled(true); + + setNonInteractive(); + mTestLooper.dispatchAll(); + + assertThat(mController.isActive()).isFalse(); + verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); + } + + @Test + public void testOnNonInteractive_activateAfterStandbyTimeout() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + mController.setEnabled(true); + + setNonInteractive(); + awaitStandbyTimeoutAlarm(); + + assertThat(mController.isActive()).isTrue(); + verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(true); + } + + @Test + public void testOnNonInteractive_doesNotActivateWhenBecomingInteractive() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + mController.setEnabled(true); + + setNonInteractive(); + advanceTime(STANDBY_TIMEOUT / 2); + setInteractive(); + verifyStandbyAlarmCancelled(); + + assertThat(mController.isActive()).isFalse(); + verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); + } + + private void verifyStandbyAlarmCancelled() { + InOrder inOrder = inOrder(mAlarmManagerMock); + inOrder.verify(mAlarmManagerMock, atLeast(0)).setExact(anyInt(), anyLong(), anyString(), + any(), any()); + inOrder.verify(mAlarmManagerMock).cancel((AlarmManager.OnAlarmListener) any()); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testOnInteractive_deactivate() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + mController.setEnabled(true); + setNonInteractive(); + setDeviceIdleMode(true); + awaitStandbyTimeoutAlarm(); + + setInteractive(); + mTestLooper.dispatchAll(); + + assertThat(mController.isActive()).isFalse(); + verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false); + } + + @Test + public void testOnDozeMaintenance_deactivate() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + mController.setEnabled(true); + mController.setActiveDuringMaintenance(false); + setNonInteractive(); + setDeviceIdleMode(true); + awaitStandbyTimeoutAlarm(); + + setDeviceIdleMode(false); + mTestLooper.dispatchAll(); + + assertThat(mController.isActive()).isFalse(); + verify(mPowerManagerInternalMock, times(1)).setLowPowerStandbyActive(false); + } + + @Test + public void testOnDozeMaintenance_activeDuringMaintenance_staysActive() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + mController.setEnabled(true); + mController.setActiveDuringMaintenance(true); + setNonInteractive(); + setDeviceIdleMode(true); + awaitStandbyTimeoutAlarm(); + + setDeviceIdleMode(false); + mTestLooper.dispatchAll(); + + assertThat(mController.isActive()).isTrue(); + verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(false); + } + + @Test + public void testOnDozeMaintenanceEnds_activate() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + mController.setEnabled(true); + setNonInteractive(); + setDeviceIdleMode(true); + awaitStandbyTimeoutAlarm(); + + setDeviceIdleMode(false); + advanceTime(1000); + setDeviceIdleMode(true); + mTestLooper.dispatchAll(); + + assertThat(mController.isActive()).isTrue(); + verify(mPowerManagerInternalMock, times(2)).setLowPowerStandbyActive(true); + } + + @Test + public void testLowPowerStandbyDisabled_doesNotActivate() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + mController.setEnabled(false); + setNonInteractive(); + + assertThat(mController.isActive()).isFalse(); + verify(mAlarmManagerMock, never()).setExact(anyInt(), anyLong(), anyString(), any(), any()); + verify(mPowerManagerInternalMock, never()).setLowPowerStandbyActive(anyBoolean()); + } + + @Test + public void testLowPowerStandbyEnabled_EnabledChangedBroadcastsAreSent() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + + BroadcastInterceptingContext.FutureIntent futureIntent = mContextSpy.nextBroadcastIntent( + PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED); + mController.setEnabled(false); + futureIntent.assertNotReceived(); + + futureIntent = mContextSpy.nextBroadcastIntent( + PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED); + mController.setEnabled(true); + assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull(); + + futureIntent = mContextSpy.nextBroadcastIntent( + PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED); + mController.setEnabled(true); + futureIntent.assertNotReceived(); + + futureIntent = mContextSpy.nextBroadcastIntent( + PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED); + + mController.setEnabled(false); + assertThat(futureIntent.get(1, TimeUnit.SECONDS)).isNotNull(); + } + + @Test + public void testSetEnabled_WhenNotSupported_DoesNotEnable() throws Exception { + setLowPowerStandbySupportedConfig(false); + mController.systemReady(); + + mController.setEnabled(true); + + assertThat(mController.isEnabled()).isFalse(); + } + + @Test + public void testIsSupported_WhenSupported() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + + assertThat(mController.isSupported()).isTrue(); + } + + @Test + public void testIsSupported_WhenNotSupported() throws Exception { + setLowPowerStandbySupportedConfig(false); + mController.systemReady(); + + assertThat(mController.isSupported()).isFalse(); + } + + @Test + public void testAllowlistChange_servicesAreNotified() throws Exception { + setLowPowerStandbySupportedConfig(true); + mController.systemReady(); + + LowPowerStandbyControllerInternal service = LocalServices.getService( + LowPowerStandbyControllerInternal.class); + service.addToAllowlist(10); + mTestLooper.dispatchAll(); + verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {10}); + + service.removeFromAllowlist(10); + mTestLooper.dispatchAll(); + verify(mPowerManagerInternalMock).setLowPowerStandbyAllowlist(new int[] {}); + } + + @Test + public void testForceActive() throws Exception { + setLowPowerStandbySupportedConfig(false); + mController.systemReady(); + + mController.forceActive(true); + mTestLooper.dispatchAll(); + + assertThat(mController.isActive()).isTrue(); + verify(mPowerManagerInternalMock).setLowPowerStandbyActive(true); + + mController.forceActive(false); + mTestLooper.dispatchAll(); + + assertThat(mController.isActive()).isFalse(); + verify(mPowerManagerInternalMock).setLowPowerStandbyActive(false); + } + + private void setLowPowerStandbySupportedConfig(boolean supported) { + when(mResourcesSpy.getBoolean( + com.android.internal.R.bool.config_lowPowerStandbySupported)) + .thenReturn(supported); + } + + private void setInteractive() throws Exception { + when(mIPowerManagerMock.isInteractive()).thenReturn(true); + mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON)); + } + + private void setNonInteractive() throws Exception { + when(mIPowerManagerMock.isInteractive()).thenReturn(false); + mContextSpy.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF)); + } + + private void setDeviceIdleMode(boolean idle) throws Exception { + when(mIPowerManagerMock.isDeviceIdleMode()).thenReturn(idle); + mContextSpy.sendBroadcast(new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)); + } + + private void advanceTime(long timeMs) { + mClock.fastForward(timeMs); + mTestLooper.dispatchAll(); + } + + /** + * Creates a mock and registers it to {@link LocalServices}. + */ + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } +} 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 d35c679c18bd..51dbd97c183b 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -16,6 +16,8 @@ package com.android.server.power; +import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; +import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING; @@ -65,6 +67,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.PowerSaveState; +import android.os.SystemClock; import android.os.UserHandle; import android.os.test.TestLooper; import android.provider.Settings; @@ -86,6 +89,7 @@ import com.android.server.power.PowerManagerService.BinderService; import com.android.server.power.PowerManagerService.Injector; import com.android.server.power.PowerManagerService.NativeWrapper; import com.android.server.power.PowerManagerService.UserSwitchedReceiver; +import com.android.server.power.PowerManagerService.WakeLock; import com.android.server.power.batterysaver.BatterySaverController; import com.android.server.power.batterysaver.BatterySaverPolicy; import com.android.server.power.batterysaver.BatterySaverStateMachine; @@ -283,6 +287,13 @@ public class PowerManagerServiceTest { void invalidateIsInteractiveCaches() { // Avoids an SELinux failure. } + + @Override + LowPowerStandbyController createLowPowerStandbyController(Context context, + Looper looper) { + return new LowPowerStandbyController(context, mTestLooper.getLooper(), + SystemClock::elapsedRealtime); + } }); return mService; } @@ -293,6 +304,7 @@ public class PowerManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.removeServiceForTest(BatteryManagerInternal.class); LocalServices.removeServiceForTest(ActivityManagerInternal.class); + LocalServices.removeServiceForTest(LowPowerStandbyControllerInternal.class); FakeSettingsProvider.clearSettingsProvider(); } @@ -1575,4 +1587,60 @@ public class PowerManagerServiceTest { .setFullPowerSavePolicy(mockSetPolicyConfig)).isTrue(); verify(mBatterySaverStateMachineMock).setFullBatterySaverPolicy(eq(mockSetPolicyConfig)); } + + @Test + public void testLowPowerStandby_whenInactive_FgsWakeLockEnabled() { + createService(); + mService.systemReady(); + WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK); + mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE); + mService.setDeviceIdleModeInternal(true); + + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testLowPowerStandby_whenActive_FgsWakeLockDisabled() { + createService(); + mService.systemReady(); + WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK); + mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE); + mService.setDeviceIdleModeInternal(true); + mService.setLowPowerStandbyActiveInternal(true); + + assertThat(wakeLock.mDisabled).isTrue(); + } + + @Test + public void testLowPowerStandby_whenActive_FgsWakeLockEnabledIfAllowlisted() { + createService(); + mService.systemReady(); + WakeLock wakeLock = acquireWakeLock("fgsWakeLock", PowerManager.PARTIAL_WAKE_LOCK); + mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_FOREGROUND_SERVICE); + mService.setDeviceIdleModeInternal(true); + mService.setLowPowerStandbyActiveInternal(true); + mService.setLowPowerStandbyAllowlistInternal(new int[]{wakeLock.mOwnerUid}); + + assertThat(wakeLock.mDisabled).isFalse(); + } + + @Test + public void testLowPowerStandby_whenActive_BoundTopWakeLockDisabled() { + createService(); + mService.systemReady(); + WakeLock wakeLock = acquireWakeLock("BoundTopWakeLock", PowerManager.PARTIAL_WAKE_LOCK); + mService.updateUidProcStateInternal(wakeLock.mOwnerUid, PROCESS_STATE_BOUND_TOP); + mService.setDeviceIdleModeInternal(true); + mService.setLowPowerStandbyActiveInternal(true); + + assertThat(wakeLock.mDisabled).isFalse(); + } + + private WakeLock acquireWakeLock(String tag, int flags) { + IBinder token = new Binder(); + String packageName = "pkg.name"; + mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName, + null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY); + return mService.findWakeLockLocked(token); + } } diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java index 09612e33b98a..a73fcb89da27 100644 --- a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java +++ b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java @@ -211,6 +211,25 @@ public class WakeLockLogTest { dumpLog(log, false)); } + @Test + public void testAddSystemWakelock() { + final int tagDatabaseSize = 6; + final int logSize = 10; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy); + + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("TagPartial", 101, + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - 101 - ACQ TagPartial (partial,system-wakelock)\n" + + " -\n" + + " Events: 1, Time-Resets: 0\n" + + " Buffer, Bytes used: 3\n", + dumpLog(log, false)); + } + private String dumpLog(WakeLockLog log, boolean includeTagDb) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); 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..e65501329cdf 100644 --- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java +++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsTracker.java @@ -69,6 +69,12 @@ 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, @@ -132,8 +138,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..6906f20f26c2 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -281,7 +281,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() { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index 90ccec852e1e..a061618b1ca7 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -69,6 +69,7 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.am.AssistDataRequester; import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks; +import com.android.server.power.LowPowerStandbyControllerInternal; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wm.ActivityAssistInfo; @@ -89,6 +90,11 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, static final int POWER_BOOST_TIMEOUT_MS = Integer.parseInt( System.getProperty("vendor.powerhal.interaction.max", "200")); static final int BOOST_TIMEOUT_MS = 300; + /** + * The maximum time an app can stay on the Low Power Standby allowlist when + * the session is shown. There to safeguard against apps that don't call hide. + */ + private static final int LOW_POWER_STANDBY_ALLOWLIST_TIMEOUT_MS = 120_000; // TODO: To avoid ap doesn't call hide, only 10 secs for now, need a better way to manage it // in the future. static final int MAX_POWER_BOOST_TIMEOUT = 10_000; @@ -124,6 +130,10 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, Executors.newSingleThreadScheduledExecutor(); private final List<VisibleActivityInfo> mVisibleActivityInfos = new ArrayList<>(); private final PowerManagerInternal mPowerManagerInternal; + private final LowPowerStandbyControllerInternal mLowPowerStandbyControllerInternal; + private final Runnable mRemoveFromLowPowerStandbyAllowlistRunnable = + this::removeFromLowPowerStandbyAllowlist; + private boolean mLowPowerStandbyAllowlisted; private PowerBoostSetter mSetPowerBoostRunnable; private final Handler mFgHandler; @@ -211,6 +221,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, mIWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); + mLowPowerStandbyControllerInternal = LocalServices.getService( + LowPowerStandbyControllerInternal.class); mAppOps = context.getSystemService(AppOpsManager.class); mFgHandler = FgThread.getHandler(); mAssistDataRequester = new AssistDataRequester(mContext, mIWindowManager, @@ -322,6 +334,15 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, mSetPowerBoostRunnable = new PowerBoostSetter( Instant.now().plusMillis(MAX_POWER_BOOST_TIMEOUT)); mFgHandler.post(mSetPowerBoostRunnable); + + if (mLowPowerStandbyControllerInternal != null) { + mLowPowerStandbyControllerInternal.addToAllowlist(mCallingUid); + mLowPowerStandbyAllowlisted = true; + mFgHandler.removeCallbacks(mRemoveFromLowPowerStandbyAllowlistRunnable); + mFgHandler.postDelayed(mRemoveFromLowPowerStandbyAllowlistRunnable, + LOW_POWER_STANDBY_ALLOWLIST_TIMEOUT_MS); + } + mCallback.onSessionShown(this); return true; } @@ -493,6 +514,9 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } // A negative value indicates canceling previous boost. mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, /* durationMs */ -1); + if (mLowPowerStandbyControllerInternal != null) { + removeFromLowPowerStandbyAllowlist(); + } mCallback.onSessionHidden(this); } if (mFullyBound) { @@ -730,6 +754,16 @@ final class VoiceInteractionSessionConnection implements ServiceConnection, } } + private void removeFromLowPowerStandbyAllowlist() { + synchronized (mLock) { + if (mLowPowerStandbyAllowlisted) { + mFgHandler.removeCallbacks(mRemoveFromLowPowerStandbyAllowlistRunnable); + mLowPowerStandbyControllerInternal.removeFromAllowlist(mCallingUid); + mLowPowerStandbyAllowlisted = false; + } + } + } + @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mLock) { diff --git a/startop/iorap/Android.bp b/startop/iorap/Android.bp deleted file mode 100644 index 4fdf34cc1814..000000000000 --- a/startop/iorap/Android.bp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -filegroup { - name: "services.startop.iorap-javasources", - srcs: ["src/**/*.java"], - path: "src", - visibility: ["//visibility:private"], -} - -filegroup { - name: "services.startop.iorap-sources", - srcs: [ - ":services.startop.iorap-javasources", - ":iorap-aidl", - ], - visibility: ["//frameworks/base/services:__subpackages__"], -} - -java_library_static { - name: "services.startop.iorap", - srcs: [":services.startop.iorap-sources"], - libs: ["services.core"], -} diff --git a/startop/iorap/TEST_MAPPING b/startop/iorap/TEST_MAPPING deleted file mode 100644 index 8c9d4dfb0894..000000000000 --- a/startop/iorap/TEST_MAPPING +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presubmit": [ - { - "name": "libiorap-java-tests" - } - ], - "imports": [ - { - "path": "system/iorap" - } - ] -} diff --git a/startop/iorap/functional_tests/Android.bp b/startop/iorap/functional_tests/Android.bp deleted file mode 100644 index 43c61551cdec..000000000000 --- a/startop/iorap/functional_tests/Android.bp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test { - name: "iorap-functional-tests", - srcs: ["src/**/*.java"], - data: [":iorap-functional-test-apps"], - static_libs: [ - // Non-test dependencies - // library under test - "services.startop.iorap", - // Test Dependencies - // test android dependencies - "platform-test-annotations", - "androidx.test.rules", - "androidx.test.ext.junit", - "androidx.test.uiautomator_uiautomator", - // test framework dependencies - "truth-prebuilt", - ], - dxflags: ["--multi-dex"], - test_suites: ["device-tests"], - compile_multilib: "both", - libs: [ - "android.test.base", - "android.test.runner", - ], - certificate: "platform", - platform_apis: true, -} diff --git a/startop/iorap/functional_tests/AndroidManifest.xml b/startop/iorap/functional_tests/AndroidManifest.xml deleted file mode 100644 index 6bddc4a39577..000000000000 --- a/startop/iorap/functional_tests/AndroidManifest.xml +++ /dev/null @@ -1,38 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<!--suppress AndroidUnknownAttribute --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.google.android.startop.iorap.tests" - android:sharedUserId="com.google.android.startop.iorap.tests.functional" - android:versionCode="1" - android:versionName="1.0" > - - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - <!--suppress AndroidDomInspection --> - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.google.android.startop.iorap.tests" /> - - <!-- - 'debuggable=true' is required to properly load mockito jvmti dependencies, - otherwise it gives the following error at runtime: - - Openjdkjvmti plugin was loaded on a non-debuggable Runtime. - Plugin was loaded too late to change runtime state to DEBUGGABLE. --> - <application android:debuggable="true"> - <uses-library android:name="android.test.runner" /> - </application> -</manifest> diff --git a/startop/iorap/functional_tests/AndroidTest.xml b/startop/iorap/functional_tests/AndroidTest.xml deleted file mode 100644 index 31d4f6c47b11..000000000000 --- a/startop/iorap/functional_tests/AndroidTest.xml +++ /dev/null @@ -1,70 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<configuration description="Runs iorap-functional-tests."> - <option name="test-suite-tag" value="apct" /> - <option name="test-suite-tag" value="apct-instrumentation" /> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="test-file-name" value="iorap-functional-tests.apk" /> - </target_preparer> - - <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> - - <target_preparer - class="com.android.tradefed.targetprep.DeviceSetup"> - - <!-- iorapd does not pick up the above changes until we restart it --> - <option name="run-command" value="stop iorapd" /> - - <!-- Clean up the existing iorap database. --> - <option name="run-command" value="rm -r /data/misc/iorapd/*" /> - <option name="run-command" value="sleep 1" /> - - <!-- Set system properties to enable perfetto tracing, readahead and detailed logging. --> - <option name="run-command" value="setprop iorapd.perfetto.enable true" /> - <option name="run-command" value="setprop iorapd.readahead.enable true" /> - <option name="run-command" value="setprop iorapd.log.verbose true" /> - - <option name="run-command" value="start iorapd" /> - - <!-- give it some time to restart the service; otherwise the first unit test might fail --> - <option name="run-command" value="sleep 1" /> - </target_preparer> - - <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> - <option name="cleanup" value="true" /> - <option name="abort-on-push-failure" value="true" /> - <option name="push-file" - key="iorap_test_app_v1.apk" - value="/data/misc/iorapd/iorap_test_app_v1.apk" /> - <option name="push-file" - key="iorap_test_app_v2.apk" - value="/data/misc/iorapd/iorap_test_app_v2.apk" /> - <option name="push-file" - key="iorap_test_app_v3.apk" - value="/data/misc/iorapd/iorap_test_app_v3.apk" /> - </target_preparer> - - <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="com.google.android.startop.iorap.tests" /> - <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> - <!-- test-timeout unit is ms, value = 30 min --> - <option name="test-timeout" value="1800000" /> - </test> - -</configuration> - diff --git a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java b/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java deleted file mode 100644 index 5352be6f283f..000000000000 --- a/startop/iorap/functional_tests/src/com/google/android/startop/iorap/IorapWorkFlowTest.java +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorapd; - -import static androidx.test.core.app.ApplicationProvider.getApplicationContext; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.uiautomator.By; -import androidx.test.uiautomator.UiDevice; -import androidx.test.uiautomator.Until; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; -import java.util.Date; -import java.util.function.BooleanSupplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.List; -import java.text.SimpleDateFormat; - -/** - * Test for the work flow of iorap. - * - * <p> This test tests the function of iorap from: - * perfetto collection -> compilation -> prefetching -> version update -> perfetto collection. - */ -@RunWith(AndroidJUnit4.class) -public class IorapWorkFlowTest { - private static final String TAG = "IorapWorkFlowTest"; - - private static final String TEST_APP_VERSION_ONE_PATH = "/data/misc/iorapd/iorap_test_app_v1.apk"; - private static final String TEST_APP_VERSION_TWO_PATH = "/data/misc/iorapd/iorap_test_app_v2.apk"; - private static final String TEST_APP_VERSION_THREE_PATH = "/data/misc/iorapd/iorap_test_app_v3.apk"; - - private static final String DB_PATH = "/data/misc/iorapd/sqlite.db"; - private static final Duration TIMEOUT = Duration.ofSeconds(300L); - - private UiDevice mDevice; - - @Before - public void setUp() throws Exception { - // Initialize UiDevice instance - mDevice = UiDevice.getInstance(getInstrumentation()); - - // Start from the home screen - mDevice.pressHome(); - - // Wait for launcher - final String launcherPackage = mDevice.getLauncherPackageName(); - assertThat(launcherPackage, notNullValue()); - mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), TIMEOUT.getSeconds()); - } - - @After - public void tearDown() throws Exception { - String packageName = "com.example.ioraptestapp"; - uninstallApk(packageName); - } - - @Test (timeout = 300000) - public void testNormalWorkFlow() throws Exception { - assertThat(mDevice, notNullValue()); - - // Install test app version one - installApk(TEST_APP_VERSION_ONE_PATH); - String packageName = "com.example.ioraptestapp"; - String activityName = "com.example.ioraptestapp.MainActivity"; - - // Perfetto trace collection phase. - assertTrue(startAppForPerfettoTrace( - packageName, activityName, /*version=*/1L)); - assertTrue(startAppForPerfettoTrace( - packageName, activityName, /*version=*/1L)); - assertTrue(startAppForPerfettoTrace( - packageName, activityName, /*version=*/1L)); - - // Trigger maintenance service for compilation. - TimeUnit.SECONDS.sleep(5L); - assertTrue(compile(packageName, activityName, /*version=*/1L)); - - // Run app with prefetching - assertTrue(startAppWithCompiledTrace( - packageName, activityName, /*version=*/1L)); - } - - @Test (timeout = 300000) - public void testUpdateApp() throws Exception { - assertThat(mDevice, notNullValue()); - - // Install test app version two, - String packageName = "com.example.ioraptestapp"; - String activityName = "com.example.ioraptestapp.MainActivity"; - installApk(TEST_APP_VERSION_TWO_PATH); - - // Perfetto trace collection phase. - assertTrue(startAppForPerfettoTrace( - packageName, activityName, /*version=*/2L)); - assertTrue(startAppForPerfettoTrace( - packageName, activityName, /*version=*/2L)); - assertTrue(startAppForPerfettoTrace( - packageName, activityName, /*version=*/2L)); - - // Trigger maintenance service for compilation. - TimeUnit.SECONDS.sleep(5L); - assertTrue(compile(packageName, activityName, /*version=*/2L)); - - // Run app with prefetching - assertTrue(startAppWithCompiledTrace( - packageName, activityName, /*version=*/2L)); - - // Update test app to version 3 - installApk(TEST_APP_VERSION_THREE_PATH); - - // Rerun app, should do pefetto tracing. - assertTrue(startAppForPerfettoTrace( - packageName, activityName, /*version=*/3L)); - } - - private static void installApk(String apkPath) throws Exception { - // Disable the selinux to allow pm install apk in the dir. - executeShellCommand("setenforce 0"); - executeShellCommand("pm install -r -d " + apkPath); - executeShellCommand("setenforce 1"); - - } - - private static void uninstallApk(String apkPath) throws Exception { - executeShellCommand("pm uninstall " + apkPath); - } - - /** - * Starts the testing app to collect the perfetto trace. - * - * @param expectPerfettoTraceCount is the expected count of perfetto traces. - */ - private boolean startAppForPerfettoTrace( - String packageName, String activityName, long version) - throws Exception { - LogcatTimestamp timestamp = runAppOnce(packageName, activityName); - return waitForPerfettoTraceSavedFromLogcat( - packageName, activityName, version, timestamp); - } - - private boolean startAppWithCompiledTrace( - String packageName, String activityName, long version) - throws Exception { - LogcatTimestamp timestamp = runAppOnce(packageName, activityName); - return waitForPrefetchingFromLogcat( - packageName, activityName, version, timestamp); - } - - private LogcatTimestamp runAppOnce(String packageName, String activityName) throws Exception { - // Close the specified app if it's open - closeApp(packageName); - LogcatTimestamp timestamp = new LogcatTimestamp(); - // Launch the specified app - startApp(packageName, activityName); - // Wait for the app to appear - mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), TIMEOUT.getSeconds()); - return timestamp; - } - - // Invokes the maintenance to compile the perfetto traces to compiled trace. - private boolean compile( - String packageName, String activityName, long version) throws Exception { - // The job id (283673059) is defined in class IorapForwardingService. - executeShellCommandViaTmpFile("cmd jobscheduler run -f android 283673059"); - return waitForFileExistence(getCompiledTracePath(packageName, activityName, version)); - } - - private String getCompiledTracePath( - String packageName, String activityName, long version) { - return String.format( - "/data/misc/iorapd/%s/%d/%s/compiled_traces/compiled_trace.pb", - packageName, version, activityName); - } - - /** - * Starts the testing app. - */ - private void startApp(String packageName, String activityName) throws Exception { - executeShellCommandViaTmpFile( - String.format("am start %s/%s", packageName, activityName)); - } - - /** - * Closes the testing app. - * <p> Keep trying to kill the process of the app until no process of the app package - * appears.</p> - */ - private void closeApp(String packageName) throws Exception { - while (true) { - String pid = executeShellCommand("pidof " + packageName); - if (pid.isEmpty()) { - Log.i(TAG, "Closed app " + packageName); - return; - } - executeShellCommand("kill -9 " + pid); - TimeUnit.SECONDS.sleep(1L); - } - } - - /** Waits for a file to appear. */ - private boolean waitForFileExistence(String fileName) throws Exception { - return retryWithTimeout(TIMEOUT, () -> { - try { - String fileExists = executeShellCommandViaTmpFile( - String.format("test -f %s; echo $?", fileName)); - Log.i(TAG, fileName + " existence is " + fileExists); - return fileExists.trim().equals("0"); - } catch (Exception e) { - Log.i(TAG, e.getMessage()); - return false; - } - }); - } - - /** Waits for the perfetto trace saved message from logcat. */ - private boolean waitForPerfettoTraceSavedFromLogcat( - String packageName, String activityName, long version, LogcatTimestamp timestamp) - throws Exception { - Pattern p = Pattern.compile(".*" - + getPerfettoTraceSavedIndicator(packageName, activityName, version) - + "(.*[.]perfetto_trace[.]pb)\n.*", Pattern.DOTALL); - - return retryWithTimeout(TIMEOUT, () -> { - try { - String log = timestamp.getLogcatAfter(); - Matcher m = p.matcher(log); - Log.d(TAG, "Tries to find perfetto trace..."); - if (!m.matches()) { - Log.i(TAG, "Cannot find perfetto trace saved in log."); - return false; - } - String filePath = m.group(1); - Log.i(TAG, "Perfetto trace is saved to " + filePath); - return true; - } catch(Exception e) { - Log.e(TAG, e.getMessage()); - return false; - } - }); - } - - private String getPerfettoTraceSavedIndicator( - String packageName, String activityName, long version) { - return String.format( - "Perfetto TraceBuffer saved to file: /data/misc/iorapd/%s/%d/%s/raw_traces/", - packageName, version, activityName); - } - - /** - * Waits for the prefetching log in the logcat. - * - * <p> When prefetching works, the perfetto traces should not be collected. </p> - */ - private boolean waitForPrefetchingFromLogcat( - String packageName, String activityName, long version, LogcatTimestamp timestamp) - throws Exception { - Pattern p = Pattern.compile( - ".*" + getReadaheadIndicator(packageName, activityName, version) + - ".*Total File Paths=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n" - + ".*Total Entries=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n" - + ".*Total Bytes=(\\d+) \\(good: (\\d+[.]?\\d*)%\\).*", - Pattern.DOTALL); - - return retryWithTimeout(TIMEOUT, () -> { - try { - String log = timestamp.getLogcatAfter(); - Matcher m = p.matcher(log); - if (!m.matches()) { - Log.i(TAG, "Cannot find readahead log."); - return false; - } - - int totalFilePath = Integer.parseInt(m.group(1)); - float totalFilePathGoodRate = Float.parseFloat(m.group(2)) / 100; - int totalEntries = Integer.parseInt(m.group(3)); - float totalEntriesGoodRate = Float.parseFloat(m.group(4)) / 100; - int totalBytes = Integer.parseInt(m.group(5)); - float totalBytesGoodRate = Float.parseFloat(m.group(6)) / 100; - - Log.i(TAG, String.format( - "totalFilePath: %d (good %.2f) totalEntries: %d (good %.2f) totalBytes: %d (good %.2f)", - totalFilePath, totalFilePathGoodRate, totalEntries, totalEntriesGoodRate, totalBytes, - totalBytesGoodRate)); - - return totalFilePath > 0 && - totalEntries > 0 && - totalBytes > 0 && - totalFilePathGoodRate > 0.5 && - totalEntriesGoodRate > 0.5 && - totalBytesGoodRate > 0.5; - } catch(Exception e) { - return false; - } - }); - } - - private static String getReadaheadIndicator( - String packageName, String activityName, long version) { - return String.format( - "Description = /data/misc/iorapd/%s/%d/%s/compiled_traces/compiled_trace.pb", - packageName, version, activityName); - } - - /** Retry until timeout. */ - private boolean retryWithTimeout(Duration timeout, BooleanSupplier supplier) throws Exception { - long totalSleepTimeSeconds = 0L; - long sleepIntervalSeconds = 2L; - while (true) { - if (supplier.getAsBoolean()) { - return true; - } - TimeUnit.SECONDS.sleep(sleepIntervalSeconds); - totalSleepTimeSeconds += sleepIntervalSeconds; - if (totalSleepTimeSeconds > timeout.getSeconds()) { - return false; - } - } - } - - /** - * Executes command in adb shell via a tmp file. - * - * <p> This should be run as root.</p> - */ - private static String executeShellCommandViaTmpFile(String cmd) throws Exception { - Log.i(TAG, "Execute via tmp file: " + cmd); - Path tmp = null; - try { - tmp = Files.createTempFile(/*prefix=*/null, /*suffix=*/".sh"); - Files.write(tmp, cmd.getBytes(StandardCharsets.UTF_8)); - tmp.toFile().setExecutable(true); - return UiDevice.getInstance( - InstrumentationRegistry.getInstrumentation()). - executeShellCommand(tmp.toString()); - } finally { - if (tmp != null) { - Files.delete(tmp); - } - } - } - - /** - * Executes command in adb shell. - * - * <p> This should be run as root.</p> - */ - private static String executeShellCommand(String cmd) throws Exception { - Log.i(TAG, "Execute: " + cmd); - return UiDevice.getInstance( - InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd); - } - - static class LogcatTimestamp { - private String epochTime; - - public LogcatTimestamp() throws Exception{ - long currentTimeMillis = System.currentTimeMillis(); - epochTime = String.format( - "%d.%03d", currentTimeMillis/1000, currentTimeMillis%1000); - Log.i(TAG, "Current logcat timestamp is " + epochTime); - } - - // For example, 1585264100.000 - public String getEpochTime() { - return epochTime; - } - - // Gets the logcat after this epoch time. - public String getLogcatAfter() throws Exception { - return executeShellCommandViaTmpFile( - "logcat -v epoch -t '" + epochTime + "'"); - } - } -} - diff --git a/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java b/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java deleted file mode 100644 index 1d38f4c1e23d..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.os.Parcelable; -import android.os.Parcel; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * Provide a hint to iorapd that an activity has transitioned state.<br /><br /> - * - * Knowledge of when an activity starts/stops can be used by iorapd to increase system - * performance (e.g. by launching perfetto tracing to record an io profile, or by - * playing back an ioprofile via readahead) over the long run.<br /><br /> - * - * /@see com.google.android.startop.iorap.IIorap#onActivityHintEvent<br /><br /> - * - * Once an activity hint is in {@link #TYPE_STARTED} it must transition to another type. - * All other states could be terminal, see below: <br /><br /> - * - * <pre> - * - * ┌──────────────────────────────────────┐ - * │ ▼ - * ┌─────────┐ ╔════════════════╗ ╔═══════════╗ - * ──▶ │ STARTED │ ──▶ ║ COMPLETED ║ ──▶ ║ CANCELLED ║ - * └─────────┘ ╚════════════════╝ ╚═══════════╝ - * │ - * │ - * ▼ - * ╔════════════════╗ - * ║ POST_COMPLETED ║ - * ╚════════════════╝ - * - * </pre> <!-- system/iorap/docs/binder/ActivityHint.dot --> - * - * @hide - */ -public class ActivityHintEvent implements Parcelable { - - public static final int TYPE_STARTED = 0; - public static final int TYPE_CANCELLED = 1; - public static final int TYPE_COMPLETED = 2; - public static final int TYPE_POST_COMPLETED = 3; - private static final int TYPE_MAX = TYPE_POST_COMPLETED; - - /** @hide */ - @IntDef(flag = true, prefix = { "TYPE_" }, value = { - TYPE_STARTED, - TYPE_CANCELLED, - TYPE_COMPLETED, - TYPE_POST_COMPLETED, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - - @Type public final int type; - public final ActivityInfo activityInfo; - - public ActivityHintEvent(@Type int type, ActivityInfo activityInfo) { - this.type = type; - this.activityInfo = activityInfo; - checkConstructorArguments(); - } - - private void checkConstructorArguments() { - CheckHelpers.checkTypeInRange(type, TYPE_MAX); - Objects.requireNonNull(activityInfo, "activityInfo"); - } - - @Override - public String toString() { - return String.format("{type: %d, activityInfo: %s}", type, activityInfo); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (other instanceof ActivityHintEvent) { - return equals((ActivityHintEvent) other); - } - return false; - } - - private boolean equals(ActivityHintEvent other) { - return type == other.type && - Objects.equals(activityInfo, other.activityInfo); - } - - //<editor-fold desc="Binder boilerplate"> - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(type); - activityInfo.writeToParcel(out, flags); - } - - private ActivityHintEvent(Parcel in) { - this.type = in.readInt(); - this.activityInfo = ActivityInfo.CREATOR.createFromParcel(in); - checkConstructorArguments(); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<ActivityHintEvent> CREATOR - = new Parcelable.Creator<ActivityHintEvent>() { - public ActivityHintEvent createFromParcel(Parcel in) { - return new ActivityHintEvent(in); - } - - public ActivityHintEvent[] newArray(int size) { - return new ActivityHintEvent[size]; - } - }; - //</editor-fold> -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java b/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java deleted file mode 100644 index f47a42cffdd8..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import java.util.Objects; - -import android.os.Parcelable; -import android.os.Parcel; - -/** - * Provide minimal information for launched activities to iorap.<br /><br /> - * - * This uniquely identifies a system-wide activity by providing the {@link #packageName} and - * {@link #activityName}. - * - * @see ActivityHintEvent - * @see AppIntentEvent - * - * @hide - */ -public class ActivityInfo implements Parcelable { - - /** The name of the package, for example {@code com.android.calculator}. */ - public final String packageName; - /** The name of the activity, for example {@code .activities.activity.MainActivity} */ - public final String activityName; - - public ActivityInfo(String packageName, String activityName) { - this.packageName = packageName; - this.activityName = activityName; - - checkConstructorArguments(); - } - - private void checkConstructorArguments() { - Objects.requireNonNull(packageName, "packageName"); - Objects.requireNonNull(activityName, "activityName"); - } - - @Override - public String toString() { - return String.format("{packageName: %s, activityName: %s}", packageName, activityName); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (other instanceof ActivityInfo) { - return equals((ActivityInfo) other); - } - return false; - } - - private boolean equals(ActivityInfo other) { - return Objects.equals(packageName, other.packageName) && - Objects.equals(activityName, other.activityName); - } - - //<editor-fold desc="Binder boilerplate"> - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(packageName); - out.writeString(activityName); - } - - private ActivityInfo(Parcel in) { - packageName = in.readString(); - activityName = in.readString(); - - checkConstructorArguments(); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<ActivityInfo> CREATOR - = new Parcelable.Creator<ActivityInfo>() { - public ActivityInfo createFromParcel(Parcel in) { - return new ActivityInfo(in); - } - - public ActivityInfo[] newArray(int size) { - return new ActivityInfo[size]; - } - }; - //</editor-fold> -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java deleted file mode 100644 index 1cd37b5546b9..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.os.Parcelable; -import android.os.Parcel; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * Notifications for iorapd specifying when a system-wide intent defaults change.<br /><br /> - * - * Intent defaults provide a mechanism for an app to register itself as an automatic handler. - * For example the camera app might be registered as the default handler for - * {@link android.provider.MediaStore#INTENT_ACTION_STILL_IMAGE_CAMERA} intent. Subsequently, - * if an arbitrary other app requests for a still image camera photo to be taken, the system - * will launch the respective default camera app to be launched to handle that request.<br /><br /> - * - * In some cases iorapd might need to know default intents, e.g. for boot-time pinning of - * applications that resolve from the default intent. If the application would now be resolved - * differently, iorapd would unpin the old application and pin the new application.<br /><br /> - * - * @hide - */ -public class AppIntentEvent implements Parcelable { - - /** @see android.content.Intent#CATEGORY_DEFAULT */ - public static final int TYPE_DEFAULT_INTENT_CHANGED = 0; - private static final int TYPE_MAX = 0; - - /** @hide */ - @IntDef(flag = true, prefix = { "TYPE_" }, value = { - TYPE_DEFAULT_INTENT_CHANGED, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - - @Type public final int type; - - public final ActivityInfo oldActivityInfo; - public final ActivityInfo newActivityInfo; - - // TODO: Probably need the corresponding action here as well. - - public static AppIntentEvent createDefaultIntentChanged(ActivityInfo oldActivityInfo, - ActivityInfo newActivityInfo) { - return new AppIntentEvent(TYPE_DEFAULT_INTENT_CHANGED, oldActivityInfo, - newActivityInfo); - } - - private AppIntentEvent(@Type int type, ActivityInfo oldActivityInfo, - ActivityInfo newActivityInfo) { - this.type = type; - this.oldActivityInfo = oldActivityInfo; - this.newActivityInfo = newActivityInfo; - - checkConstructorArguments(); - } - - private void checkConstructorArguments() { - CheckHelpers.checkTypeInRange(type, TYPE_MAX); - Objects.requireNonNull(oldActivityInfo, "oldActivityInfo"); - Objects.requireNonNull(oldActivityInfo, "newActivityInfo"); - } - - @Override - public String toString() { - return String.format("{oldActivityInfo: %s, newActivityInfo: %s}", oldActivityInfo, - newActivityInfo); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (other instanceof AppIntentEvent) { - return equals((AppIntentEvent) other); - } - return false; - } - - private boolean equals(AppIntentEvent other) { - return type == other.type && - Objects.equals(oldActivityInfo, other.oldActivityInfo) && - Objects.equals(newActivityInfo, other.newActivityInfo); - } - - //<editor-fold desc="Binder boilerplate"> - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(type); - oldActivityInfo.writeToParcel(out, flags); - newActivityInfo.writeToParcel(out, flags); - } - - private AppIntentEvent(Parcel in) { - this.type = in.readInt(); - this.oldActivityInfo = ActivityInfo.CREATOR.createFromParcel(in); - this.newActivityInfo = ActivityInfo.CREATOR.createFromParcel(in); - - checkConstructorArguments(); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<AppIntentEvent> CREATOR - = new Parcelable.Creator<AppIntentEvent>() { - public AppIntentEvent createFromParcel(Parcel in) { - return new AppIntentEvent(in); - } - - public AppIntentEvent[] newArray(int size) { - return new AppIntentEvent[size]; - } - }; - //</editor-fold> -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java deleted file mode 100644 index 8263e0af4422..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.annotation.LongDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.proto.ProtoOutputStream; - -import com.android.server.wm.ActivityMetricsLaunchObserver; -import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; -import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; -import java.util.Objects; - -/** - * Provide a hint to iorapd that an app launch sequence has transitioned state.<br /><br /> - * - * Knowledge of when an activity starts/stops can be used by iorapd to increase system - * performance (e.g. by launching perfetto tracing to record an io profile, or by - * playing back an ioprofile via readahead) over the long run.<br /><br /> - * - * /@see com.google.android.startop.iorap.IIorap#onAppLaunchEvent <br /><br /> - * @see com.android.server.wm.ActivityMetricsLaunchObserver - * ActivityMetricsLaunchObserver for the possible event states. - * @hide - */ -public abstract class AppLaunchEvent implements Parcelable { - @LongDef - @Retention(RetentionPolicy.SOURCE) - public @interface SequenceId {} - - public final @SequenceId - long sequenceId; - - protected AppLaunchEvent(@SequenceId long sequenceId) { - this.sequenceId = sequenceId; - } - - @Override - public boolean equals(Object other) { - if (other instanceof AppLaunchEvent) { - return equals((AppLaunchEvent) other); - } - return false; - } - - protected boolean equals(AppLaunchEvent other) { - return sequenceId == other.sequenceId; - } - - - @Override - public String toString() { - return getClass().getSimpleName() + - "{" + "sequenceId=" + Long.toString(sequenceId) + - toStringBody() + "}"; - } - - protected String toStringBody() { return ""; }; - - // List of possible variants: - - public static final class IntentStarted extends AppLaunchEvent { - @NonNull - public final Intent intent; - public final long timestampNs; - - public IntentStarted(@SequenceId long sequenceId, - Intent intent, - long timestampNs) { - super(sequenceId); - this.intent = intent; - this.timestampNs = timestampNs; - - Objects.requireNonNull(intent, "intent"); - } - - @Override - public boolean equals(Object other) { - if (other instanceof IntentStarted) { - return intent.equals(((IntentStarted)other).intent) && - timestampNs == ((IntentStarted)other).timestampNs && - super.equals(other); - } - return false; - } - - @Override - protected String toStringBody() { - return ", intent=" + intent.toString() + - " , timestampNs=" + Long.toString(timestampNs); - } - - - @Override - protected void writeToParcelImpl(Parcel p, int flags) { - super.writeToParcelImpl(p, flags); - IntentProtoParcelable.write(p, intent, flags); - p.writeLong(timestampNs); - } - - IntentStarted(Parcel p) { - super(p); - intent = IntentProtoParcelable.create(p); - timestampNs = p.readLong(); - } - } - - public static final class IntentFailed extends AppLaunchEvent { - public IntentFailed(@SequenceId long sequenceId) { - super(sequenceId); - } - - @Override - public boolean equals(Object other) { - if (other instanceof IntentFailed) { - return super.equals(other); - } - return false; - } - - IntentFailed(Parcel p) { - super(p); - } - } - - public static abstract class BaseWithActivityRecordData extends AppLaunchEvent { - public final @NonNull - @ActivityRecordProto byte[] activityRecordSnapshot; - - protected BaseWithActivityRecordData(@SequenceId long sequenceId, - @NonNull @ActivityRecordProto byte[] snapshot) { - super(sequenceId); - activityRecordSnapshot = snapshot; - - Objects.requireNonNull(snapshot, "snapshot"); - } - - @Override - public boolean equals(Object other) { - if (other instanceof BaseWithActivityRecordData) { - return (Arrays.equals(activityRecordSnapshot, - ((BaseWithActivityRecordData)other).activityRecordSnapshot)) && - super.equals(other); - } - return false; - } - - @Override - protected String toStringBody() { - return ", " + new String(activityRecordSnapshot); - } - - @Override - protected void writeToParcelImpl(Parcel p, int flags) { - super.writeToParcelImpl(p, flags); - ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags); - } - - BaseWithActivityRecordData(Parcel p) { - super(p); - activityRecordSnapshot = ActivityRecordProtoParcelable.create(p); - } - } - - public static final class ActivityLaunched extends BaseWithActivityRecordData { - public final @ActivityMetricsLaunchObserver.Temperature - int temperature; - - public ActivityLaunched(@SequenceId long sequenceId, - @NonNull @ActivityRecordProto byte[] snapshot, - @ActivityMetricsLaunchObserver.Temperature int temperature) { - super(sequenceId, snapshot); - this.temperature = temperature; - } - - @Override - public boolean equals(Object other) { - if (other instanceof ActivityLaunched) { - return temperature == ((ActivityLaunched)other).temperature && - super.equals(other); - } - return false; - } - - @Override - protected String toStringBody() { - return super.toStringBody() + ", temperature=" + Integer.toString(temperature); - } - - @Override - protected void writeToParcelImpl(Parcel p, int flags) { - super.writeToParcelImpl(p, flags); - p.writeInt(temperature); - } - - ActivityLaunched(Parcel p) { - super(p); - temperature = p.readInt(); - } - } - - public static final class ActivityLaunchFinished extends BaseWithActivityRecordData { - public final long timestampNs; - - public ActivityLaunchFinished(@SequenceId long sequenceId, - @NonNull @ActivityRecordProto byte[] snapshot, - long timestampNs) { - super(sequenceId, snapshot); - this.timestampNs = timestampNs; - } - - @Override - public boolean equals(Object other) { - if (other instanceof ActivityLaunchFinished) { - return timestampNs == ((ActivityLaunchFinished)other).timestampNs && - super.equals(other); - } - return false; - } - - @Override - protected String toStringBody() { - return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs); - } - - @Override - protected void writeToParcelImpl(Parcel p, int flags) { - super.writeToParcelImpl(p, flags); - p.writeLong(timestampNs); - } - - ActivityLaunchFinished(Parcel p) { - super(p); - timestampNs = p.readLong(); - } - } - - public static class ActivityLaunchCancelled extends AppLaunchEvent { - public final @Nullable @ActivityRecordProto byte[] activityRecordSnapshot; - - public ActivityLaunchCancelled(@SequenceId long sequenceId, - @Nullable @ActivityRecordProto byte[] snapshot) { - super(sequenceId); - activityRecordSnapshot = snapshot; - } - - @Override - public boolean equals(Object other) { - if (other instanceof ActivityLaunchCancelled) { - return Arrays.equals(activityRecordSnapshot, - ((ActivityLaunchCancelled)other).activityRecordSnapshot) && - super.equals(other); - } - return false; - } - - @Override - protected String toStringBody() { - return super.toStringBody() + ", " + new String(activityRecordSnapshot); - } - - @Override - protected void writeToParcelImpl(Parcel p, int flags) { - super.writeToParcelImpl(p, flags); - if (activityRecordSnapshot != null) { - p.writeBoolean(true); - ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags); - } else { - p.writeBoolean(false); - } - } - - ActivityLaunchCancelled(Parcel p) { - super(p); - if (p.readBoolean()) { - activityRecordSnapshot = ActivityRecordProtoParcelable.create(p); - } else { - activityRecordSnapshot = null; - } - } - } - - public static final class ReportFullyDrawn extends BaseWithActivityRecordData { - public final long timestampNs; - - public ReportFullyDrawn(@SequenceId long sequenceId, - @NonNull @ActivityRecordProto byte[] snapshot, - long timestampNs) { - super(sequenceId, snapshot); - this.timestampNs = timestampNs; - } - - @Override - public boolean equals(Object other) { - if (other instanceof ReportFullyDrawn) { - return timestampNs == ((ReportFullyDrawn)other).timestampNs && - super.equals(other); - } - return false; - } - - @Override - protected String toStringBody() { - return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs); - } - - @Override - protected void writeToParcelImpl(Parcel p, int flags) { - super.writeToParcelImpl(p, flags); - p.writeLong(timestampNs); - } - - ReportFullyDrawn(Parcel p) { - super(p); - timestampNs = p.readLong(); - } - } - - @Override - public @ContentsFlags int describeContents() { return 0; } - - @Override - public void writeToParcel(Parcel p, @WriteFlags int flags) { - p.writeInt(getTypeIndex()); - - writeToParcelImpl(p, flags); - } - - - public static Creator<AppLaunchEvent> CREATOR = - new Creator<AppLaunchEvent>() { - @Override - public AppLaunchEvent createFromParcel(Parcel source) { - int typeIndex = source.readInt(); - - Class<?> kls = getClassFromTypeIndex(typeIndex); - if (kls == null) { - throw new IllegalArgumentException("Invalid type index: " + typeIndex); - } - - try { - return (AppLaunchEvent) kls.getConstructor(Parcel.class).newInstance(source); - } catch (InstantiationException e) { - throw new AssertionError(e); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } catch (InvocationTargetException e) { - throw new AssertionError(e); - } catch (NoSuchMethodException e) { - throw new AssertionError(e); - } - } - - @Override - public AppLaunchEvent[] newArray(int size) { - return new AppLaunchEvent[0]; - } - }; - - protected void writeToParcelImpl(Parcel p, int flags) { - p.writeLong(sequenceId); - } - - protected AppLaunchEvent(Parcel p) { - sequenceId = p.readLong(); - } - - private int getTypeIndex() { - for (int i = 0; i < sTypes.length; ++i) { - if (sTypes[i].equals(this.getClass())) { - return i; - } - } - throw new AssertionError("sTypes did not include this type: " + this.getClass()); - } - - private static @Nullable Class<?> getClassFromTypeIndex(int typeIndex) { - if (typeIndex >= 0 && typeIndex < sTypes.length) { - return sTypes[typeIndex]; - } - return null; - } - - // Index position matters: It is used to encode the specific type in parceling. - // Keep up-to-date with C++ side. - private static Class<?>[] sTypes = new Class[] { - IntentStarted.class, - IntentFailed.class, - ActivityLaunched.class, - ActivityLaunchFinished.class, - ActivityLaunchCancelled.class, - ReportFullyDrawn.class, - }; - - public static class ActivityRecordProtoParcelable { - public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot, - int flags) { - p.writeByteArray(activityRecordSnapshot); - } - - public static @ActivityRecordProto byte[] create(Parcel p) { - byte[] data = p.createByteArray(); - - return data; - } - } - - public static class IntentProtoParcelable { - private static final int INTENT_PROTO_CHUNK_SIZE = 1024; - - public static void write(Parcel p, @NonNull Intent intent, int flags) { - // There does not appear to be a way to 'reset' a ProtoOutputBuffer stream, - // so create a new one every time. - final ProtoOutputStream protoOutputStream = - new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE); - // Write this data out as the top-most IntentProto (i.e. it is not a sub-object). - intent.dumpDebug(protoOutputStream); - final byte[] bytes = protoOutputStream.getBytes(); - - p.writeByteArray(bytes); - } - - // TODO: Should be mockable for testing? - // We cannot deserialize in the platform because we don't have a 'readFromProto' - // code. - public static @NonNull Intent create(Parcel p) { - // This will "read" the correct amount of data, but then we discard it. - byte[] data = p.createByteArray(); - - // Never called by real code in a platform, this binder API is implemented only in C++. - return new Intent("<cannot deserialize IntentProto>"); - } - } -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java b/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java deleted file mode 100644 index 34aedd7685d8..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.startop.iorap; - -/** - * Convenience short-hand to throw {@link IllegalAccessException} when the arguments - * are out-of-range. - */ -public class CheckHelpers { - /** @throws IllegalAccessException if {@param type} is not in {@code [0..maxValue]} */ - public static void checkTypeInRange(int type, int maxValue) { - if (type < 0) { - throw new IllegalArgumentException( - String.format("type must be non-negative (value=%d)", type)); - } - if (type > maxValue) { - throw new IllegalArgumentException( - String.format("type out of range (value=%d, max=%d)", type, maxValue)); - } - } - - /** @throws IllegalAccessException if {@param state} is not in {@code [0..maxValue]} */ - public static void checkStateInRange(int state, int maxValue) { - if (state < 0) { - throw new IllegalArgumentException( - String.format("state must be non-negative (value=%d)", state)); - } - if (state > maxValue) { - throw new IllegalArgumentException( - String.format("state out of range (value=%d, max=%d)", state, maxValue)); - } - } -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/DexOptEvent.java b/startop/iorap/src/com/google/android/startop/iorap/DexOptEvent.java deleted file mode 100644 index 72c5eaa84c96..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/DexOptEvent.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.annotation.NonNull; -import android.os.Parcelable; -import android.os.Parcel; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * Notifications for iorapd specifying when a package is updated by dexopt service.<br /><br /> - * - * @hide - */ -public class DexOptEvent implements Parcelable { - public static final int TYPE_PACKAGE_UPDATE = 0; - private static final int TYPE_MAX = 0; - - /** @hide */ - @IntDef(flag = true, prefix = { "TYPE_" }, value = { - TYPE_PACKAGE_UPDATE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - - @Type public final int type; - public final String packageName; - - @NonNull - public static DexOptEvent createPackageUpdate(String packageName) { - return new DexOptEvent(TYPE_PACKAGE_UPDATE, packageName); - } - - private DexOptEvent(@Type int type, String packageName) { - this.type = type; - this.packageName = packageName; - - checkConstructorArguments(); - } - - private void checkConstructorArguments() { - CheckHelpers.checkTypeInRange(type, TYPE_MAX); - Objects.requireNonNull(packageName, "packageName"); - } - - @Override - public String toString() { - return String.format("{DexOptEvent: packageName: %s}", packageName); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (other instanceof DexOptEvent) { - return equals((DexOptEvent) other); - } - return false; - } - - private boolean equals(DexOptEvent other) { - return packageName.equals(other.packageName); - } - - //<editor-fold desc="Binder boilerplate"> - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(type); - out.writeString(packageName); - } - - private DexOptEvent(Parcel in) { - this.type = in.readInt(); - this.packageName = in.readString(); - - checkConstructorArguments(); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<DexOptEvent> CREATOR - = new Parcelable.Creator<DexOptEvent>() { - public DexOptEvent createFromParcel(Parcel in) { - return new DexOptEvent(in); - } - - public DexOptEvent[] newArray(int size) { - return new DexOptEvent[size]; - } - }; - //</editor-fold> -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java deleted file mode 100644 index dcaff26b79c6..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Intent; -import android.util.Log; - -import com.android.server.wm.ActivityMetricsLaunchObserver; - -import java.io.StringWriter; -import java.io.PrintWriter; - -/** - * A validator to check the correctness of event sequence during app startup. - * - * <p> A valid state transition of event sequence is shown as the following: - * - * <pre> - * - * +--------------------+ - * | | - * | INIT | - * | | - * +--------------------+ - * | - * | - * ↓ - * +--------------------+ - * | | - * +-------------------| INTENT_STARTED | ←--------------------------------+ - * | | | | - * | +--------------------+ | - * | | | - * | | | - * ↓ ↓ | - * +--------------------+ +--------------------+ | - * | | | | | - * | INTENT_FAILED | | ACTIVITY_LAUNCHED |------------------+ | - * | | | | | | - * +--------------------+ +--------------------+ | | - * | | | | - * | ↓ ↓ | - * | +--------------------+ +--------------------+ | - * | | | | | | - * +------------------ | ACTIVITY_FINISHED | | ACTIVITY_CANCELLED | | - * | | | | | | - * | +--------------------+ +--------------------+ | - * | | | | - * | | | | - * | ↓ | | - * | +--------------------+ | | - * | | | | | - * | | REPORT_FULLY_DRAWN | | | - * | | | | | - * | +--------------------+ | | - * | | | | - * | | | | - * | ↓ | | - * | +--------------------+ | | - * | | | | | - * +-----------------→ | END |←-----------------+ | - * | | | - * +--------------------+ | - * | | - * | | - * | | - * +--------------------------------------------- - * - * <p> END is not a real state in implementation. All states that points to END directly - * could transition to INTENT_STARTED. - * - * <p> If any bad transition happened, the state becomse UNKNOWN. The UNKNOWN state - * could be accumulated, because during the UNKNOWN state more IntentStarted may - * be triggered. To recover from UNKNOWN to INIT, all the accumualted IntentStarted - * should termniate. - * - * <p> During UNKNOWN state, each IntentStarted increases the accumulation, and any of - * IntentFailed, ActivityLaunchCancelled and ActivityFinished decreases the accumulation. - * ReportFullyDrawn doesn't impact the accumulation. - */ -public class EventSequenceValidator implements ActivityMetricsLaunchObserver { - static final String TAG = "EventSequenceValidator"; - /** $> adb shell 'setprop log.tag.EventSequenceValidator VERBOSE' */ - public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private State state = State.INIT; - private long accIntentStartedEvents = 0; - - @Override - public void onIntentStarted(@NonNull Intent intent, long timestampNs) { - if (state == State.UNKNOWN) { - logWarningWithStackTrace("IntentStarted during UNKNOWN. " + intent); - incAccIntentStartedEvents(); - return; - } - - if (state != State.INIT && - state != State.INTENT_FAILED && - state != State.ACTIVITY_CANCELLED && - state != State.ACTIVITY_FINISHED && - state != State.REPORT_FULLY_DRAWN) { - logWarningWithStackTrace( - String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED)); - incAccIntentStartedEvents(); - incAccIntentStartedEvents(); - return; - } - - Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_STARTED)); - state = State.INTENT_STARTED; - } - - @Override - public void onIntentFailed() { - if (state == State.UNKNOWN) { - logWarningWithStackTrace("onIntentFailed during UNKNOWN."); - decAccIntentStartedEvents(); - return; - } - if (state != State.INTENT_STARTED) { - logWarningWithStackTrace( - String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED)); - incAccIntentStartedEvents(); - return; - } - - Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_FAILED)); - state = State.INTENT_FAILED; - } - - @Override - public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, - @Temperature int temperature) { - if (state == State.UNKNOWN) { - logWarningWithStackTrace("onActivityLaunched during UNKNOWN."); - return; - } - if (state != State.INTENT_STARTED) { - logWarningWithStackTrace( - String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED)); - incAccIntentStartedEvents(); - return; - } - - Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_LAUNCHED)); - state = State.ACTIVITY_LAUNCHED; - } - - @Override - public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) { - if (state == State.UNKNOWN) { - logWarningWithStackTrace("onActivityLaunchCancelled during UNKNOWN."); - decAccIntentStartedEvents(); - return; - } - if (state != State.ACTIVITY_LAUNCHED) { - logWarningWithStackTrace( - String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED)); - incAccIntentStartedEvents(); - return; - } - - Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_CANCELLED)); - state = State.ACTIVITY_CANCELLED; - } - - @Override - public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity, - long timestampNs) { - if (state == State.UNKNOWN) { - logWarningWithStackTrace("onActivityLaunchFinished during UNKNOWN."); - decAccIntentStartedEvents(); - return; - } - - if (state != State.ACTIVITY_LAUNCHED) { - logWarningWithStackTrace( - String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED)); - incAccIntentStartedEvents(); - return; - } - - Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_FINISHED)); - state = State.ACTIVITY_FINISHED; - } - - @Override - public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity, - long timestampNs) { - if (state == State.UNKNOWN) { - logWarningWithStackTrace("onReportFullyDrawn during UNKNOWN."); - return; - } - if (state == State.INIT) { - return; - } - - if (state != State.ACTIVITY_FINISHED) { - logWarningWithStackTrace( - String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN)); - return; - } - - Log.i(TAG, String.format("Transition from %s to %s", state, State.REPORT_FULLY_DRAWN)); - state = State.REPORT_FULLY_DRAWN; - } - - enum State { - INIT, - INTENT_STARTED, - INTENT_FAILED, - ACTIVITY_LAUNCHED, - ACTIVITY_CANCELLED, - ACTIVITY_FINISHED, - REPORT_FULLY_DRAWN, - UNKNOWN, - } - - private void incAccIntentStartedEvents() { - if (accIntentStartedEvents < 0) { - throw new AssertionError("The number of unknowns cannot be negative"); - } - if (accIntentStartedEvents == 0) { - state = State.UNKNOWN; - } - ++accIntentStartedEvents; - Log.i(TAG, - String.format("inc AccIntentStartedEvents to %d", accIntentStartedEvents)); - } - - private void decAccIntentStartedEvents() { - if (accIntentStartedEvents <= 0) { - throw new AssertionError("The number of unknowns cannot be negative"); - } - if(accIntentStartedEvents == 1) { - state = State.INIT; - } - --accIntentStartedEvents; - Log.i(TAG, - String.format("dec AccIntentStartedEvents to %d", accIntentStartedEvents)); - } - - private void logWarningWithStackTrace(String log) { - if (DEBUG) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - new Throwable("EventSequenceValidator#getStackTrace").printStackTrace(pw); - Log.wtf(TAG, String.format("%s\n%s", log, sw)); - } - } -} - diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java deleted file mode 100644 index 77046f2276e9..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +++ /dev/null @@ -1,806 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; -// TODO: rename to com.android.server.startop.iorap - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.job.JobInfo; -import android.app.job.JobParameters; -import android.app.job.JobScheduler; -import android.app.job.JobService; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.IBinder.DeathRecipient; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemProperties; -import android.provider.DeviceConfig; -import android.util.ArraySet; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.IoThread; -import com.android.server.LocalServices; -import com.android.server.SystemService; -import com.android.server.pm.BackgroundDexOptService; -import com.android.server.pm.PackageManagerService; -import com.android.server.wm.ActivityMetricsLaunchObserver; -import com.android.server.wm.ActivityMetricsLaunchObserverRegistry; -import com.android.server.wm.ActivityTaskManagerInternal; - -import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BooleanSupplier; - -/** - * System-server-local proxy into the {@code IIorap} native service. - */ -public class IorapForwardingService extends SystemService { - - public static final String TAG = "IorapForwardingService"; - /** $> adb shell 'setprop log.tag.IorapForwardingService VERBOSE' */ - public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - /** $> adb shell 'setprop ro.iorapd.enable true' */ - private static boolean IS_ENABLED = SystemProperties.getBoolean("ro.iorapd.enable", true); - /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */ - private static boolean WTF_CRASH = SystemProperties.getBoolean( - "iorapd.forwarding_service.wtf_crash", false); - private static final Duration TIMEOUT = Duration.ofSeconds(600L); - - // "Unique" job ID from the service name. Also equal to 283673059. - public static final int JOB_ID_IORAPD = encodeEnglishAlphabetStringIntoInt("iorapd"); - // Run every 24 hours. - public static final long JOB_INTERVAL_MS = TimeUnit.HOURS.toMillis(24); - - private IIorap mIorapRemote; - private final Object mLock = new Object(); - /** Handle onBinderDeath by periodically trying to reconnect. */ - private final Handler mHandler = - new BinderConnectionHandler(IoThread.getHandler().getLooper()); - - private volatile IorapdJobService mJobService; // Write-once (null -> non-null forever). - private volatile static IorapForwardingService sSelfService; // Write once (null -> non-null). - - - /** - * Atomics set to true if the JobScheduler requests an abort. - */ - private final AtomicBoolean mAbortIdleCompilation = new AtomicBoolean(false); - - /** - * 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 IorapForwardingService(Context context) { - super(context); - - if (DEBUG) { - Log.v(TAG, "IorapForwardingService (Context=" + context.toString() + ")"); - } - - if (sSelfService != null) { - throw new AssertionError("only one service instance allowed"); - } - sSelfService = this; - } - - //<editor-fold desc="Providers"> - /* - * Providers for external dependencies: - * - These are marked as protected to allow tests to inject different values via mocks. - */ - - @VisibleForTesting - protected ActivityMetricsLaunchObserverRegistry provideLaunchObserverRegistry() { - ActivityTaskManagerInternal amtInternal = - LocalServices.getService(ActivityTaskManagerInternal.class); - ActivityMetricsLaunchObserverRegistry launchObserverRegistry = - amtInternal.getLaunchObserverRegistry(); - return launchObserverRegistry; - } - - @VisibleForTesting - protected IIorap provideIorapRemote() { - IIorap iorap; - try { - iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); - } catch (ServiceManager.ServiceNotFoundException e) { - Log.w(TAG, e.getMessage()); - return null; - } - - try { - iorap.asBinder().linkToDeath(provideDeathRecipient(), /*flags*/0); - } catch (RemoteException e) { - handleRemoteError(e); - return null; - } - - return iorap; - } - - @VisibleForTesting - protected DeathRecipient provideDeathRecipient() { - return new DeathRecipient() { - @Override - public void binderDied() { - Log.w(TAG, "iorapd has died"); - retryConnectToRemoteAndConfigure(/*attempts*/0); - - if (mJobService != null) { - mJobService.onIorapdDisconnected(); - } - } - }; - } - - @VisibleForTesting - protected boolean isIorapEnabled() { - // These two mendel flags should match those in iorapd native process - // system/iorapd/src/common/property.h - boolean isTracingEnabled = - getMendelFlag("iorap_perfetto_enable", "iorapd.perfetto.enable", false); - boolean isReadAheadEnabled = - getMendelFlag("iorap_readahead_enable", "iorapd.readahead.enable", false); - // Same as the property in iorapd.rc -- disabling this will mean the 'iorapd' binder process - // never comes up, so all binder connections will fail indefinitely. - return IS_ENABLED && (isTracingEnabled || isReadAheadEnabled); - } - - private boolean getMendelFlag(String mendelFlag, String sysProperty, boolean defaultValue) { - // TODO(yawanng) use DeviceConfig to get mendel property. - // DeviceConfig doesn't work and the reason is not clear. - // Provider service is already up before IORapForwardService. - String mendelProperty = "persist.device_config." - + DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT - + "." - + mendelFlag; - return SystemProperties.getBoolean(mendelProperty, - SystemProperties.getBoolean(sysProperty, defaultValue)); - } - - //</editor-fold> - - @Override - public void onStart() { - if (DEBUG) { - Log.v(TAG, "onStart"); - } - - retryConnectToRemoteAndConfigure(/*attempts*/0); - } - - @Override - public void onBootPhase(int phase) { - if (phase == PHASE_BOOT_COMPLETED) { - if (DEBUG) { - Log.v(TAG, "onBootPhase(PHASE_BOOT_COMPLETED)"); - } - - if (isIorapEnabled()) { - // Set up a recurring background job. This has to be done in a later phase since it - // has a dependency the job scheduler. - // - // Doing this too early can result in a ServiceNotFoundException for 'jobservice' - // or a null reference for #getSystemService(JobScheduler.class) - mJobService = new IorapdJobService(getContext()); - } - } - } - - private class BinderConnectionHandler extends Handler { - public BinderConnectionHandler(android.os.Looper looper) { - super(looper); - } - - public static final int MESSAGE_BINDER_CONNECT = 0; - - private int mAttempts = 0; - - @Override - public void handleMessage(android.os.Message message) { - switch (message.what) { - case MESSAGE_BINDER_CONNECT: - if (!retryConnectToRemoteAndConfigure(mAttempts)) { - mAttempts++; - } else { - mAttempts = 0; - } - break; - default: - throw new AssertionError("Unknown message: " + message.toString()); - } - } - } - - /** - * Handle iorapd shutdowns and crashes, by attempting to reconnect - * until the service is reached again. - * - * <p>The first connection attempt is synchronous, - * subsequent attempts are done by posting delayed tasks to the IoThread.</p> - * - * @return true if connection succeeded now, or false if it failed now [and needs to requeue]. - */ - private boolean retryConnectToRemoteAndConfigure(int attempts) { - final int sleepTime = 1000; // ms - - if (DEBUG) { - Log.v(TAG, "retryConnectToRemoteAndConfigure - attempt #" + attempts); - } - - if (connectToRemoteAndConfigure()) { - return true; - } - - // Either 'iorapd' is stuck in a crash loop (ouch!!) or we manually - // called 'adb shell stop iorapd' , which means this would loop until it comes back - // up. - // - // TODO: it would be good to get nodified of 'adb shell stop iorapd' to avoid - // printing this warning. - if (DEBUG) { - Log.v(TAG, "Failed to connect to iorapd, is it down? Delay for " + sleepTime); - } - - // Use a handler instead of Thread#sleep to avoid backing up the binder thread - // when this is called from the death recipient callback. - mHandler.sendMessageDelayed( - mHandler.obtainMessage(BinderConnectionHandler.MESSAGE_BINDER_CONNECT), - sleepTime); - - return false; - - // Log.e(TAG, "Can't connect to iorapd - giving up after " + attempts + " attempts"); - } - - private boolean connectToRemoteAndConfigure() { - synchronized (mLock) { - // Synchronize against any concurrent calls to this via the DeathRecipient. - return connectToRemoteAndConfigureLocked(); - } - } - - private boolean connectToRemoteAndConfigureLocked() { - if (!isIorapEnabled()) { - if (DEBUG) { - Log.v(TAG, "connectToRemoteAndConfigure - iorapd is disabled, skip rest of work"); - } - // When we see that iorapd is disabled (when system server comes up), - // it stays disabled permanently until the next system server reset. - - // TODO: consider listening to property changes as a callback, then we can - // be more dynamic about handling enable/disable. - return true; - } - - // Connect to the native binder service. - mIorapRemote = provideIorapRemote(); - if (mIorapRemote == null) { - if (DEBUG) { - Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?"); - } - return false; - } - invokeRemote(mIorapRemote, - (IIorap remote) -> remote.setTaskListener(new RemoteTaskListener()) ); - registerInProcessListenersLocked(); - - Log.i(TAG, "Connected to iorapd native service."); - - return true; - } - - private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver(); - private final EventSequenceValidator mEventSequenceValidator = new EventSequenceValidator(); - private final DexOptPackagesUpdated mDexOptPackagesUpdated = new DexOptPackagesUpdated(); - private boolean mRegisteredListeners = false; - - private void registerInProcessListenersLocked() { - if (mRegisteredListeners) { - // Listeners are registered only once (idempotent operation). - // - // Today listeners are tolerant of the remote side going away - // by handling remote errors. - // - // We could try to 'unregister' the listener when we get a binder disconnect, - // but we'd still have to handle the case of encountering synchronous errors so - // it really wouldn't be a win (other than having less log spew). - return; - } - - // Listen to App Launch Sequence events from ActivityTaskManager, - // and forward them to the native binder service. - ActivityMetricsLaunchObserverRegistry launchObserverRegistry = - provideLaunchObserverRegistry(); - launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver); - launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator); - - BackgroundDexOptService.getService().addPackagesUpdatedListener( - mDexOptPackagesUpdated); - - - mRegisteredListeners = true; - } - - private class DexOptPackagesUpdated implements BackgroundDexOptService.PackagesUpdatedListener { - @Override - public void onPackagesUpdated(ArraySet<String> updatedPackages) { - String[] updated = updatedPackages.toArray(new String[0]); - for (String packageName : updated) { - Log.d(TAG, "onPackagesUpdated: " + packageName); - invokeRemote(mIorapRemote, - (IIorap remote) -> - remote.onDexOptEvent(RequestId.nextValueForSequence(), - DexOptEvent.createPackageUpdate(packageName)) - ); - } - } - } - - private class AppLaunchObserver implements ActivityMetricsLaunchObserver { - // We add a synthetic sequence ID here to make it easier to differentiate new - // launch sequences on the native side. - private @AppLaunchEvent.SequenceId long mSequenceId = -1; - - // All callbacks occur on the same background thread. Don't synchronize explicitly. - - @Override - public void onIntentStarted(@NonNull Intent intent, long timestampNs) { - // #onIntentStarted [is the only transition that] initiates a new launch sequence. - ++mSequenceId; - - if (DEBUG) { - Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s, %d)", - mSequenceId, intent, timestampNs)); - } - - invokeRemote(mIorapRemote, - (IIorap remote) -> - remote.onAppLaunchEvent(RequestId.nextValueForSequence(), - new AppLaunchEvent.IntentStarted(mSequenceId, intent, timestampNs)) - ); - } - - @Override - public void onIntentFailed() { - if (DEBUG) { - Log.v(TAG, String.format("AppLaunchObserver#onIntentFailed(%d)", mSequenceId)); - } - - invokeRemote(mIorapRemote, - (IIorap remote) -> - remote.onAppLaunchEvent(RequestId.nextValueForSequence(), - new AppLaunchEvent.IntentFailed(mSequenceId)) - ); - } - - @Override - public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, - @Temperature int temperature) { - if (DEBUG) { - Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunched(%d, %s, %d)", - mSequenceId, activity, temperature)); - } - - invokeRemote(mIorapRemote, - (IIorap remote) -> - remote.onAppLaunchEvent(RequestId.nextValueForSequence(), - new AppLaunchEvent.ActivityLaunched(mSequenceId, activity, temperature)) - ); - } - - @Override - public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) { - if (DEBUG) { - Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchCancelled(%d, %s)", - mSequenceId, activity)); - } - - invokeRemote(mIorapRemote, - (IIorap remote) -> - remote.onAppLaunchEvent(RequestId.nextValueForSequence(), - new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, - activity))); - } - - @Override - public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity, - long timestampNs) { - if (DEBUG) { - Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s, %d)", - mSequenceId, activity, timestampNs)); - } - - invokeRemote(mIorapRemote, - (IIorap remote) -> - remote.onAppLaunchEvent(RequestId.nextValueForSequence(), - new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, - activity, - timestampNs)) - ); - } - - @Override - public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity, - long timestampNs) { - if (DEBUG) { - Log.v(TAG, String.format("AppLaunchObserver#onReportFullyDrawn(%d, %s, %d)", - mSequenceId, activity, timestampNs)); - } - - invokeRemote(mIorapRemote, - (IIorap remote) -> - remote.onAppLaunchEvent(RequestId.nextValueForSequence(), - new AppLaunchEvent.ReportFullyDrawn(mSequenceId, activity, timestampNs)) - ); - } - } - - /** - * Debugging: - * - * $> adb shell dumpsys jobscheduler - * - * Search for 'IorapdJobServiceProxy'. - * - * JOB #1000/283673059: 6e54ed android/com.google.android.startop.iorap.IorapForwardingService$IorapdJobServiceProxy - * ^ ^ ^ - * (uid, job id) ComponentName(package/class) - * - * Forcing the job to be run, ignoring constraints: - * - * $> adb shell cmd jobscheduler run -f android 283673059 - * ^ ^ - * package job_id - * - * ------------------------------------------------------------ - * - * This class is instantiated newly by the JobService every time - * it wants to run a new job. - * - * We need to forward invocations to the current running instance of - * IorapForwardingService#IorapdJobService. - * - * Visibility: Must be accessible from android.app.AppComponentFactory - */ - public static class IorapdJobServiceProxy extends JobService { - - public IorapdJobServiceProxy() { - getActualIorapdJobService().bindProxy(this); - } - - - @NonNull - private IorapdJobService getActualIorapdJobService() { - // Can't ever be null, because the guarantee is that the - // IorapForwardingService is always running. - // We are in the same process as Job Service. - return sSelfService.mJobService; - } - - // Called by system to start the job. - @Override - public boolean onStartJob(JobParameters params) { - return getActualIorapdJobService().onStartJob(params); - } - - // Called by system to prematurely stop the job. - @Override - public boolean onStopJob(JobParameters params) { - return getActualIorapdJobService().onStopJob(params); - } - } - - private class IorapdJobService extends JobService { - private final ComponentName IORAPD_COMPONENT_NAME; - - private final Object mLock = new Object(); - // Jobs currently running remotely on iorapd. - // They were started by the JobScheduler and need to be finished. - private final HashMap<RequestId, JobParameters> mRunningJobs = new HashMap<>(); - - private final JobInfo IORAPD_JOB_INFO; - - private volatile IorapdJobServiceProxy mProxy; - - public void bindProxy(IorapdJobServiceProxy proxy) { - mProxy = proxy; - } - - // Create a new job service which immediately schedules a 24-hour idle maintenance mode - // background job to execute. - public IorapdJobService(Context context) { - if (DEBUG) { - Log.v(TAG, "IorapdJobService (Context=" + context.toString() + ")"); - } - - // Schedule the proxy class to be instantiated by the JobScheduler - // when it is time to invoke background jobs for IorapForwardingService. - - - // This also needs a BIND_JOB_SERVICE permission in - // frameworks/base/core/res/AndroidManifest.xml - IORAPD_COMPONENT_NAME = new ComponentName(context, IorapdJobServiceProxy.class); - - JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_IORAPD, IORAPD_COMPONENT_NAME); - builder.setPeriodic(JOB_INTERVAL_MS); - - builder.setRequiresCharging(true); - builder.setRequiresDeviceIdle(true); - - builder.setRequiresStorageNotLow(true); - - IORAPD_JOB_INFO = builder.build(); - - JobScheduler js = context.getSystemService(JobScheduler.class); - js.schedule(IORAPD_JOB_INFO); - Log.d(TAG, - "BgJob Scheduled (jobId=" + JOB_ID_IORAPD - + ", interval: " + JOB_INTERVAL_MS + "ms)"); - } - - // Called by system to start the job. - @Override - public boolean onStartJob(JobParameters params) { - // Tell iorapd to start a background job. - Log.d(TAG, "Starting background job: " + params.toString()); - - mAbortIdleCompilation.set(false); - // PackageManagerService starts before IORap service. - PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); - List<String> pkgs = pm.getAllPackages(); - runIdleCompilationAsync(params, pkgs); - return true; - } - - private void runIdleCompilationAsync(final JobParameters params, final List<String> pkgs) { - new Thread("IORap_IdleCompilation") { - @Override - public void run() { - for (int i = 0; i < pkgs.size(); i++) { - String pkg = pkgs.get(i); - if (mAbortIdleCompilation.get()) { - Log.i(TAG, "The idle compilation is aborted"); - return; - } - - // We wait until that job's sequence ID returns to us with 'Completed', - RequestId request; - synchronized (mLock) { - // TODO: would be cleaner if we got the request from the - // 'invokeRemote' function. Better yet, consider - // a Pair<RequestId, Future<TaskResult>> or similar. - request = RequestId.nextValueForSequence(); - mRunningJobs.put(request, params); - } - - Log.i(TAG, String.format("IORap compile package: %s, [%d/%d]", - pkg, i + 1, pkgs.size())); - boolean shouldUpdateVersions = (i == 0); - if (!invokeRemote(mIorapRemote, (IIorap remote) -> - remote.onJobScheduledEvent(request, - JobScheduledEvent.createIdleMaintenance( - JobScheduledEvent.TYPE_START_JOB, - params, - pkg, - shouldUpdateVersions)))) { - synchronized (mLock) { - mRunningJobs.remove(request); // Avoid memory leaks. - } - } - - // Wait until the job is complete and removed from the running jobs. - retryWithTimeout(TIMEOUT, () -> { - synchronized (mLock) { - return !mRunningJobs.containsKey(request); - } - }); - } - - // Finish the job after all packages are compiled. - if (mProxy != null) { - mProxy.jobFinished(params, /*reschedule*/false); - } - } - }.start(); - } - - /** Retry until timeout. */ - private boolean retryWithTimeout(final Duration timeout, BooleanSupplier supplier) { - long totalSleepTimeMs = 0L; - long sleepIntervalMs = 10L; - while (true) { - if (supplier.getAsBoolean()) { - return true; - } - try { - TimeUnit.MILLISECONDS.sleep(sleepIntervalMs); - } catch (InterruptedException e) { - Log.e(TAG, e.getMessage()); - return false; - } - - totalSleepTimeMs += sleepIntervalMs; - if (totalSleepTimeMs > timeout.toMillis()) { - return false; - } - } - } - - // Called by system to prematurely stop the job. - @Override - public boolean onStopJob(JobParameters params) { - // As this is unexpected behavior, print a warning. - Log.w(TAG, "onStopJob(params=" + params.toString() + ")"); - mAbortIdleCompilation.set(true); - - // Yes, retry the job at a later time no matter what. - return true; - } - - // Listen to *all* task completes for all requests. - // The majority of these might be unrelated to background jobs. - public void onIorapdTaskCompleted(RequestId requestId) { - JobParameters jobParameters; - synchronized (mLock) { - jobParameters = mRunningJobs.remove(requestId); - } - - // Typical case: This was a task callback unrelated to our jobs. - if (jobParameters == null) { - return; - } - - if (DEBUG) { - Log.v(TAG, - String.format("IorapdJobService#onIorapdTaskCompleted(%s), found params=%s", - requestId, jobParameters)); - } - - Log.d(TAG, "Finished background job: " + jobParameters.toString()); - } - - public void onIorapdDisconnected() { - synchronized (mLock) { - mRunningJobs.clear(); - } - - if (DEBUG) { - Log.v(TAG, String.format("IorapdJobService#onIorapdDisconnected")); - } - - // TODO: should we try to resubmit all incomplete jobs after it's reconnected? - } - } - - private class RemoteTaskListener extends ITaskListener.Stub { - @Override - public void onProgress(RequestId requestId, TaskResult result) throws RemoteException { - if (DEBUG) { - Log.v(TAG, - String.format("RemoteTaskListener#onProgress(%s, %s)", requestId, result)); - } - - // TODO: implement rest. - } - - @Override - public void onComplete(RequestId requestId, TaskResult result) throws RemoteException { - if (DEBUG) { - Log.v(TAG, - String.format("RemoteTaskListener#onComplete(%s, %s)", requestId, result)); - } - - if (mJobService != null) { - mJobService.onIorapdTaskCompleted(requestId); - } - - // TODO: implement rest. - } - } - - /** Allow passing lambdas to #invokeRemote */ - private interface RemoteRunnable { - // TODO: run(RequestId) ? - void run(IIorap iorap) throws RemoteException; - } - - // Always pass in the iorap directly here to avoid data race. - private static boolean invokeRemote(IIorap iorap, RemoteRunnable r) { - if (iorap == null) { - Log.w(TAG, "IIorap went to null in this thread, drop invokeRemote."); - return false; - } - try { - r.run(iorap); - return true; - } catch (RemoteException e) { - // This could be a logic error (remote side returning error), which we need to fix. - // - // This could also be a DeadObjectException in which case its probably just iorapd - // being manually restarted. - // - // Don't make any assumption, since DeadObjectException could also mean iorapd crashed - // unexpectedly. - // - // DeadObjectExceptions are recovered from using DeathRecipient and #linkToDeath. - handleRemoteError(e); - return false; - } - } - - private static void handleRemoteError(Throwable t) { - if (WTF_CRASH) { - // In development modes, we just want to crash. - throw new AssertionError("unexpected remote error", t); - } else { - // Log to wtf which gets sent to dropbox, and in system_server this does not crash. - Log.wtf(TAG, t); - } - } - - // Encode A-Z bitstring into bits. Every character is bits. - // Characters outside of the range [a,z] are considered out of range. - // - // The least significant bits hold the last character. - // First 2 bits are left as 0. - private static int encodeEnglishAlphabetStringIntoInt(String name) { - int value = 0; - - final int CHARS_PER_INT = 6; - final int BITS_PER_CHAR = 5; - // Note: 2 top bits are unused, this also means our values are non-negative. - final char CHAR_LOWER = 'a'; - final char CHAR_UPPER = 'z'; - - if (name.length() > CHARS_PER_INT) { - throw new IllegalArgumentException( - "String too long. Cannot encode more than 6 chars: " + name); - } - - for (int i = 0; i < name.length(); ++i) { - char c = name.charAt(i); - - if (c < CHAR_LOWER || c > CHAR_UPPER) { - throw new IllegalArgumentException("String has out-of-range [a-z] chars: " + name); - } - - // Avoid sign extension during promotion. - int cur_value = (c & 0xFFFF) - (CHAR_LOWER & 0xFFFF); - if (cur_value >= (1 << BITS_PER_CHAR)) { - throw new AssertionError("wtf? i=" + i + ", name=" + name); - } - - value = value << BITS_PER_CHAR; - value = value | cur_value; - } - - return value; - } -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/JobScheduledEvent.java b/startop/iorap/src/com/google/android/startop/iorap/JobScheduledEvent.java deleted file mode 100644 index b91dd71fd28c..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/JobScheduledEvent.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.app.job.JobParameters; -import android.annotation.NonNull; -import android.os.Parcelable; -import android.os.Parcel; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Forward JobService events to iorapd. <br /><br /> - * - * iorapd sometimes need to use background jobs. Forwarding these events to iorapd - * notifies iorapd when it is an opportune time to execute these background jobs. - * - * @hide - */ -public class JobScheduledEvent implements Parcelable { - - /** JobService#onJobStarted */ - public static final int TYPE_START_JOB = 0; - /** JobService#onJobStopped */ - public static final int TYPE_STOP_JOB = 1; - private static final int TYPE_MAX = 1; - - /** @hide */ - @IntDef(flag = true, prefix = { "TYPE_" }, value = { - TYPE_START_JOB, - TYPE_STOP_JOB, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - - @Type public final int type; - - /** @see JobParameters#getJobId() */ - public final int jobId; - - public final String packageName; - - public final boolean shouldUpdateVersions; - - /** Device is 'idle' and it's charging (plugged in). */ - public static final int SORT_IDLE_MAINTENANCE = 0; - private static final int SORT_MAX = 0; - - /** @hide */ - @IntDef(flag = true, prefix = { "SORT_" }, value = { - SORT_IDLE_MAINTENANCE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Sort {} - - /** - * Roughly corresponds to the {@code extras} fields in a JobParameters. - */ - @Sort public final int sort; - - /** - * Creates a {@link #SORT_IDLE_MAINTENANCE} event from the type and job parameters. - * - * Only the job ID is retained from {@code jobParams}, all other param info is dropped. - */ - @NonNull - public static JobScheduledEvent createIdleMaintenance( - @Type int type, JobParameters jobParams, String packageName, boolean shouldUpdateVersions) { - return new JobScheduledEvent( - type, jobParams.getJobId(), SORT_IDLE_MAINTENANCE, packageName, shouldUpdateVersions); - } - - private JobScheduledEvent(@Type int type, - int jobId, - @Sort int sort, - String packageName, - boolean shouldUpdateVersions) { - this.type = type; - this.jobId = jobId; - this.sort = sort; - this.packageName = packageName; - this.shouldUpdateVersions = shouldUpdateVersions; - - checkConstructorArguments(); - } - - private void checkConstructorArguments() { - CheckHelpers.checkTypeInRange(type, TYPE_MAX); - // No check for 'jobId': any int is valid. - CheckHelpers.checkTypeInRange(sort, SORT_MAX); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (other instanceof JobScheduledEvent) { - return equals((JobScheduledEvent) other); - } - return false; - } - - private boolean equals(JobScheduledEvent other) { - return type == other.type && - jobId == other.jobId && - sort == other.sort && - packageName.equals(other.packageName) && - shouldUpdateVersions == other.shouldUpdateVersions; - } - - @Override - public String toString() { - return String.format( - "{type: %d, jobId: %d, sort: %d, packageName: %s, shouldUpdateVersions %b}", - type, jobId, sort, packageName, shouldUpdateVersions); - } - - //<editor-fold desc="Binder boilerplate"> - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(type); - out.writeInt(jobId); - out.writeInt(sort); - out.writeString(packageName); - out.writeBoolean(shouldUpdateVersions); - - // We do not parcel the entire JobParameters here because there is no C++ equivalent - // of that class [which the iorapd side of the binder interface requires]. - } - - private JobScheduledEvent(Parcel in) { - this.type = in.readInt(); - this.jobId = in.readInt(); - this.sort = in.readInt(); - this.packageName = in.readString(); - this.shouldUpdateVersions = in.readBoolean(); - - checkConstructorArguments(); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<JobScheduledEvent> CREATOR - = new Parcelable.Creator<JobScheduledEvent>() { - public JobScheduledEvent createFromParcel(Parcel in) { - return new JobScheduledEvent(in); - } - - public JobScheduledEvent[] newArray(int size) { - return new JobScheduledEvent[size]; - } - }; - //</editor-fold> -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java b/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java deleted file mode 100644 index aa4eea716363..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.annotation.NonNull; -import android.os.Parcelable; -import android.os.Parcel; -import android.net.Uri; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Objects; - -/** - * Forward package manager events to iorapd. <br /><br /> - * - * Knowing when packages are modified by the system are a useful tidbit to help with performance: - * for example when a package is replaced, it could be a hint used to invalidate any collected - * io profiles used for prefetching or pinning. - * - * @hide - */ -public class PackageEvent implements Parcelable { - - /** @see android.content.Intent#ACTION_PACKAGE_REPLACED */ - public static final int TYPE_REPLACED = 0; - private static final int TYPE_MAX = 0; - - /** @hide */ - @IntDef(flag = true, prefix = { "TYPE_" }, value = { - TYPE_REPLACED, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - - @Type public final int type; - - /** The path that a package is installed in, for example {@code /data/app/.../base.apk}. */ - public final Uri packageUri; - /** The name of the package, for example {@code com.android.calculator}. */ - public final String packageName; - - @NonNull - public static PackageEvent createReplaced(Uri packageUri, String packageName) { - return new PackageEvent(TYPE_REPLACED, packageUri, packageName); - } - - private PackageEvent(@Type int type, Uri packageUri, String packageName) { - this.type = type; - this.packageUri = packageUri; - this.packageName = packageName; - - checkConstructorArguments(); - } - - private void checkConstructorArguments() { - CheckHelpers.checkTypeInRange(type, TYPE_MAX); - Objects.requireNonNull(packageUri, "packageUri"); - Objects.requireNonNull(packageName, "packageName"); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (other instanceof PackageEvent) { - return equals((PackageEvent) other); - } - return false; - } - - private boolean equals(PackageEvent other) { - return type == other.type && - Objects.equals(packageUri, other.packageUri) && - Objects.equals(packageName, other.packageName); - } - - @Override - public String toString() { - return String.format("{packageUri: %s, packageName: %s}", packageUri, packageName); - } - - //<editor-fold desc="Binder boilerplate"> - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(type); - packageUri.writeToParcel(out, flags); - out.writeString(packageName); - } - - private PackageEvent(Parcel in) { - this.type = in.readInt(); - this.packageUri = Uri.CREATOR.createFromParcel(in); - this.packageName = in.readString(); - - checkConstructorArguments(); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<PackageEvent> CREATOR - = new Parcelable.Creator<PackageEvent>() { - public PackageEvent createFromParcel(Parcel in) { - return new PackageEvent(in); - } - - public PackageEvent[] newArray(int size) { - return new PackageEvent[size]; - } - }; - //</editor-fold> -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java deleted file mode 100644 index 503e1c633581..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.os.Parcelable; -import android.os.Parcel; - -import android.annotation.NonNull; - -/** - * Uniquely identify an {@link com.google.android.startop.iorap.IIorap} method invocation, - * used for asynchronous callbacks by the server. <br /><br /> - * - * As all system server binder calls must be {@code oneway}, this means all invocations - * into {@link com.google.android.startop.iorap.IIorap} are non-blocking. The request ID - * exists to associate all calls with their respective callbacks in - * {@link com.google.android.startop.iorap.ITaskListener}. - * - * @see com.google.android.startop.iorap.IIorap - * - * @hide - */ -public class RequestId implements Parcelable { - - public final long requestId; - - private static Object mLock = new Object(); - private static long mNextRequestId = 0; - - /** - * Create a monotonically increasing request ID.<br /><br /> - * - * It is invalid to re-use the same request ID for multiple method calls on - * {@link com.google.android.startop.iorap.IIorap}; a new request ID must be created - * each time. - */ - @NonNull public static RequestId nextValueForSequence() { - long currentRequestId; - synchronized (mLock) { - currentRequestId = mNextRequestId; - ++mNextRequestId; - } - return new RequestId(currentRequestId); - } - - private RequestId(long requestId) { - this.requestId = requestId; - - checkConstructorArguments(); - } - - private void checkConstructorArguments() { - if (requestId < 0) { - throw new IllegalArgumentException("request id must be non-negative"); - } - } - - @Override - public String toString() { - return String.format("{requestId: %d}", requestId); - } - - @Override - public int hashCode() { - return Long.hashCode(requestId); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (other instanceof RequestId) { - return equals((RequestId) other); - } - return false; - } - - private boolean equals(RequestId other) { - return requestId == other.requestId; - } - - - //<editor-fold desc="Binder boilerplate"> - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeLong(requestId); - } - - private RequestId(Parcel in) { - requestId = in.readLong(); - - checkConstructorArguments(); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<RequestId> CREATOR - = new Parcelable.Creator<RequestId>() { - public RequestId createFromParcel(Parcel in) { - return new RequestId(in); - } - - public RequestId[] newArray(int size) { - return new RequestId[size]; - } - }; - //</editor-fold> -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java deleted file mode 100644 index 75d47f9e3d17..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.os.Parcelable; -import android.os.Parcel; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Forward system service events to iorapd. - * - * @see com.android.server.SystemService - * - * @hide - */ -public class SystemServiceEvent implements Parcelable { - - /** @see com.android.server.SystemService#onBootPhase */ - public static final int TYPE_BOOT_PHASE = 0; - /** @see com.android.server.SystemService#onStart */ - public static final int TYPE_START = 1; - private static final int TYPE_MAX = TYPE_START; - - /** @hide */ - @IntDef(flag = true, prefix = { "TYPE_" }, value = { - TYPE_BOOT_PHASE, - TYPE_START, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - - @Type public final int type; - - // TODO: do we want to pass the exact build phase enum? - - public SystemServiceEvent(@Type int type) { - this.type = type; - checkConstructorArguments(); - } - - private void checkConstructorArguments() { - CheckHelpers.checkTypeInRange(type, TYPE_MAX); - } - - @Override - public String toString() { - return String.format("{type: %d}", type); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (other instanceof SystemServiceEvent) { - return equals((SystemServiceEvent) other); - } - return false; - } - - private boolean equals(SystemServiceEvent other) { - return type == other.type; - } - - //<editor-fold desc="Binder boilerplate"> - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(type); - } - - private SystemServiceEvent(Parcel in) { - this.type = in.readInt(); - checkConstructorArguments(); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<SystemServiceEvent> CREATOR - = new Parcelable.Creator<SystemServiceEvent>() { - public SystemServiceEvent createFromParcel(Parcel in) { - return new SystemServiceEvent(in); - } - - public SystemServiceEvent[] newArray(int size) { - return new SystemServiceEvent[size]; - } - }; - //</editor-fold> -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java deleted file mode 100644 index 2e7bafe7bdbf..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.os.Parcelable; -import android.os.Parcel; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Forward user events to iorapd.<br /><br /> - * - * Knowledge of the logged-in user is reserved to be used to set-up appropriate policies - * by iorapd (e.g. to handle user default pinned applications changing). - * - * @see com.android.server.SystemService - * - * @hide - */ -public class SystemServiceUserEvent implements Parcelable { - - /** @see com.android.server.SystemService#onUserStarting */ - public static final int TYPE_START_USER = 0; - /** @see com.android.server.SystemService#onUserUnlocking */ - public static final int TYPE_UNLOCK_USER = 1; - /** @see com.android.server.SystemService#onUserSwitching*/ - public static final int TYPE_SWITCH_USER = 2; - /** @see com.android.server.SystemService#onUserStopping */ - public static final int TYPE_STOP_USER = 3; - /** @see com.android.server.SystemService#onUserStopped */ - public static final int TYPE_CLEANUP_USER = 4; - private static final int TYPE_MAX = TYPE_CLEANUP_USER; - - /** @hide */ - @IntDef(flag = true, prefix = { "TYPE_" }, value = { - TYPE_START_USER, - TYPE_UNLOCK_USER, - TYPE_SWITCH_USER, - TYPE_STOP_USER, - TYPE_CLEANUP_USER, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface Type {} - - @Type public final int type; - public final int userHandle; - - public SystemServiceUserEvent(@Type int type, int userHandle) { - this.type = type; - this.userHandle = userHandle; - checkConstructorArguments(); - } - - private void checkConstructorArguments() { - CheckHelpers.checkTypeInRange(type, TYPE_MAX); - if (userHandle < 0) { - throw new IllegalArgumentException("userHandle must be non-negative"); - } - } - - @Override - public String toString() { - return String.format("{type: %d, userHandle: %d}", type, userHandle); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (other instanceof SystemServiceUserEvent) { - return equals((SystemServiceUserEvent) other); - } - return false; - } - - private boolean equals(SystemServiceUserEvent other) { - return type == other.type && - userHandle == other.userHandle; - } - - //<editor-fold desc="Binder boilerplate"> - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(type); - out.writeInt(userHandle); - } - - private SystemServiceUserEvent(Parcel in) { - this.type = in.readInt(); - this.userHandle = in.readInt(); - checkConstructorArguments(); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<SystemServiceUserEvent> CREATOR - = new Parcelable.Creator<SystemServiceUserEvent>() { - public SystemServiceUserEvent createFromParcel(Parcel in) { - return new SystemServiceUserEvent(in); - } - - public SystemServiceUserEvent[] newArray(int size) { - return new SystemServiceUserEvent[size]; - } - }; - //</editor-fold> -} diff --git a/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java b/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java deleted file mode 100644 index b5fd6d8d1c45..000000000000 --- a/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.startop.iorap; - -import android.os.Parcelable; -import android.os.Parcel; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Result data accompanying a request for {@link com.google.android.startop.iorap.ITaskListener} - * callbacks.<br /><br /> - * - * Following {@link com.google.android.startop.iorap.IIorap} method invocation, - * iorapd will issue in-order callbacks for that corresponding {@link RequestId}.<br /><br /> - * - * State transitions are as follows: <br /><br /> - * - * <pre> - * ┌─────────────────────────────┐ - * │ ▼ - * ┌───────┐ ┌─────────┐ ╔═══════════╗ - * ──▶ │ BEGAN │ ──▶ │ ONGOING │ ──▶ ║ COMPLETED ║ - * └───────┘ └─────────┘ ╚═══════════╝ - * │ │ - * │ │ - * ▼ │ - * ╔═══════╗ │ - * ──▶ ║ ERROR ║ ◀─────┘ - * ╚═══════╝ - * - * </pre> <!-- system/iorap/docs/binder/TaskResult.dot --> - * - * @hide - */ -public class TaskResult implements Parcelable { - - public static final int STATE_BEGAN = 0; - public static final int STATE_ONGOING = 1; - public static final int STATE_COMPLETED = 2; - public static final int STATE_ERROR = 3; - private static final int STATE_MAX = STATE_ERROR; - - /** @hide */ - @IntDef(flag = true, prefix = { "STATE_" }, value = { - STATE_BEGAN, - STATE_ONGOING, - STATE_COMPLETED, - STATE_ERROR, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface State {} - - @State public final int state; - - @Override - public String toString() { - return String.format("{state: %d}", state); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } else if (other instanceof TaskResult) { - return equals((TaskResult) other); - } - return false; - } - - private boolean equals(TaskResult other) { - return state == other.state; - } - - public TaskResult(@State int state) { - this.state = state; - - checkConstructorArguments(); - } - - private void checkConstructorArguments() { - CheckHelpers.checkStateInRange(state, STATE_MAX); - } - - //<editor-fold desc="Binder boilerplate"> - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(state); - } - - private TaskResult(Parcel in) { - state = in.readInt(); - - checkConstructorArguments(); - } - - @Override - public int describeContents() { - return 0; - } - - public static final Parcelable.Creator<TaskResult> CREATOR - = new Parcelable.Creator<TaskResult>() { - public TaskResult createFromParcel(Parcel in) { - return new TaskResult(in); - } - - public TaskResult[] newArray(int size) { - return new TaskResult[size]; - } - }; - //</editor-fold> -} diff --git a/startop/iorap/stress/Android.bp b/startop/iorap/stress/Android.bp deleted file mode 100644 index 6e8725d091fb..000000000000 --- a/startop/iorap/stress/Android.bp +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -cc_binary { - name: "iorap.stress.memory", - srcs: ["main_memory.cc"], - - cflags: [ - "-Wall", - "-Wextra", - "-Werror", - "-Wno-unused-parameter" - ], - - shared_libs: [ - "libbase" - ], - - host_supported: true, -} diff --git a/startop/iorap/stress/main_memory.cc b/startop/iorap/stress/main_memory.cc deleted file mode 100644 index 1f268619e4d9..000000000000 --- a/startop/iorap/stress/main_memory.cc +++ /dev/null @@ -1,126 +0,0 @@ -// -// Copyright (C) 2020 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include <chrono> -#include <fstream> -#include <iostream> -#include <random> -#include <string> - -#include <string.h> -#include <stdlib.h> -#include <sys/mman.h> - -#include <android-base/parseint.h> - -static constexpr size_t kBytesPerMb = 1048576; -const size_t kMemoryAllocationSize = 2 * 1024 * kBytesPerMb; - -#define USE_MLOCKALL 0 - -std::string GetProcessStatus(const char* key) { - // Build search pattern of key and separator. - std::string pattern(key); - pattern.push_back(':'); - - // Search for status lines starting with pattern. - std::ifstream fs("/proc/self/status"); - std::string line; - while (std::getline(fs, line)) { - if (strncmp(pattern.c_str(), line.c_str(), pattern.size()) == 0) { - // Skip whitespace in matching line (if any). - size_t pos = line.find_first_not_of(" \t", pattern.size()); - if (pos == std::string::npos) { - break; - } - return std::string(line, pos); - } - } - return "<unknown>"; -} - -int main(int argc, char** argv) { - size_t allocationSize = 0; - if (argc >= 2) { - if (!android::base::ParseUint(argv[1], /*out*/&allocationSize)) { - std::cerr << "Failed to parse the allocation size (must be 0,MAX_SIZE_T)" << std::endl; - return 1; - } - } else { - allocationSize = kMemoryAllocationSize; - } - - void* mem = malloc(allocationSize); - if (mem == nullptr) { - std::cerr << "Malloc failed" << std::endl; - return 1; - } - - volatile int* imem = static_cast<int *>(mem); // don't optimize out memory usage - - size_t imemCount = allocationSize / sizeof(int); - - std::cout << "Allocated " << allocationSize << " bytes" << std::endl; - - auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); - std::mt19937 mt_rand(seed); - - size_t randPrintCount = 10; - - // Write random numbers: - // * Ensures each page is resident - // * Avoids zeroed out pages (zRAM) - // * Avoids same-page merging - for (size_t i = 0; i < imemCount; ++i) { - imem[i] = mt_rand(); - - if (i < randPrintCount) { - std::cout << "Generated random value: " << imem[i] << std::endl; - } - } - -#if USE_MLOCKALL - /* - * Lock all pages from the address space of this process. - */ - if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) { - std::cerr << "Mlockall failed" << std::endl; - return 1; - } -#else - // Use mlock because of the predictable VmLck size. - // Using mlockall tends to bring in anywhere from 2-2.5GB depending on the device. - if (mlock(mem, allocationSize) != 0) { - std::cerr << "Mlock failed" << std::endl; - return 1; - } -#endif - - // Validate memory is actually resident and locked with: - // $> cat /proc/$(pidof iorap.stress.memory)/status | grep VmLck - std::cout << "Locked memory (VmLck) = " << GetProcessStatus("VmLck") << std::endl; - - std::cout << "Press any key to terminate" << std::endl; - int any_input; - std::cin >> any_input; - - std::cout << "Terminating..." << std::endl; - - munlockall(); - free(mem); - - return 0; -} diff --git a/startop/iorap/tests/Android.bp b/startop/iorap/tests/Android.bp deleted file mode 100644 index ad3d001ad7d4..000000000000 --- a/startop/iorap/tests/Android.bp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (C) 2018 The Android Open Source Project -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// TODO: once b/80095087 is fixed, rewrite this back to android_test -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -java_library { - name: "libiorap-java-test-lib", - srcs: ["src/**/*.kt"], - static_libs: [ - // Non-test dependencies - // library under test - "services.startop.iorap", - // need the system_server code to be on the classpath, - "services.core", - // Test Dependencies - // test android dependencies - "platform-test-annotations", - "androidx.test.rules", - // test framework dependencies - "mockito-target-inline-minus-junit4", - // "mockito-target-minus-junit4", - // Mockito also requires JNI (see Android.mk) - // and android:debuggable=true (see AndroidManifest.xml) - "truth-prebuilt", - ], - // sdk_version: "current", - // certificate: "platform", - libs: [ - "android.test.base", - "android.test.runner", - ], - // test_suites: ["device-tests"], -} - -android_test { - name: "libiorap-java-tests", - dxflags: ["--multi-dex"], - test_suites: ["device-tests"], - static_libs: ["libiorap-java-test-lib"], - compile_multilib: "both", - jni_libs: [ - "libdexmakerjvmtiagent", - "libstaticjvmtiagent", - "libmultiplejvmtiagentsinterferenceagent", - ], - libs: [ - "android.test.base", - "android.test.runner", - ], - // Use private APIs - certificate: "platform", - platform_apis: true, -} diff --git a/startop/iorap/tests/AndroidManifest.xml b/startop/iorap/tests/AndroidManifest.xml deleted file mode 100644 index b967e7207a3f..000000000000 --- a/startop/iorap/tests/AndroidManifest.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<!--suppress AndroidUnknownAttribute --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.google.android.startop.iorap.tests" - android:sharedUserId="com.google.android.startop.iorap.tests" - android:versionCode="1" - android:versionName="1.0" > - - <!--suppress AndroidDomInspection --> - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.google.android.startop.iorap.tests" /> - - <!-- - 'debuggable=true' is required to properly load mockito jvmti dependencies, - otherwise it gives the following error at runtime: - - Openjdkjvmti plugin was loaded on a non-debuggable Runtime. - Plugin was loaded too late to change runtime state to DEBUGGABLE. --> - <application android:debuggable="true"> - <uses-library android:name="android.test.runner" /> - </application> -</manifest> diff --git a/startop/iorap/tests/AndroidTest.xml b/startop/iorap/tests/AndroidTest.xml deleted file mode 100644 index 6102c44e61bf..000000000000 --- a/startop/iorap/tests/AndroidTest.xml +++ /dev/null @@ -1,66 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2018 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<configuration description="Runs libiorap-java-tests."> - <option name="test-suite-tag" value="apct" /> - <option name="test-suite-tag" value="apct-instrumentation" /> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="test-file-name" value="libiorap-java-tests.apk" /> - </target_preparer> - - <!-- - Our IIorapIntegrationTest.kt requires setlinux to be disabled: - it connects to the iorapd binder service but this requires selinux permissions: - - avc: denied { find } for service=iorapd pid=2738 uid=10050 - scontext=u:r:platform_app:s0:c512,c768 tcontext=u:object_r:iorapd_service:s0 - tclass=service_manager permissive=0 - --> - <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"> - </target_preparer> - - <!-- do not use DeviceSetup#set-property because it reboots the device b/136200738. - furthermore the changes in /data/local.prop don't actually seem to get picked up. - --> - <target_preparer - class="com.android.tradefed.targetprep.DeviceSetup"> - <!-- we need this magic flag, otherwise it always reboots and breaks the selinux --> - <option name="force-skip-system-props" value="true" /> - - <!-- Crash instead of using Log.wtf within the system_server iorap code. --> - <option name="run-command" value="setprop iorapd.forwarding_service.wtf_crash true" /> - <!-- IIorapd has fake behavior: it doesn't do anything but reply with 'DONE' status --> - <option name="run-command" value="setprop iorapd.binder.fake true" /> - - <!-- iorapd does not pick up the above changes until we restart it --> - <option name="run-command" value="stop iorapd" /> - <option name="run-command" value="start iorapd" /> - <!-- give it some time to restart the service; otherwise the first unit test might fail --> - <option name="run-command" value="sleep 1" /> - </target_preparer> - - <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="com.google.android.startop.iorap.tests" /> - <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> - </test> - - <!-- using DeviceSetup again does not work. we simply leave the device in a semi-bad - state. there is no way to clean this up as far as I know. - --> - -</configuration> - diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt deleted file mode 100644 index 51e407d4cbff..000000000000 --- a/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.google.android.startop.iorap - -import android.content.Intent; -import android.net.Uri -import android.os.Parcel -import android.os.Parcelable -import androidx.test.filters.SmallTest -import com.google.android.startop.iorap.AppLaunchEvent; -import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunched -import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunchCancelled -import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunchFinished -import com.google.android.startop.iorap.AppLaunchEvent.IntentStarted; -import com.google.android.startop.iorap.AppLaunchEvent.IntentFailed; -import com.google.android.startop.iorap.AppLaunchEvent.ReportFullyDrawn -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized - - -/** - * Basic unit tests to test all of the [AppLaunchEvent]s in [com.google.android.startop.iorap]. - */ -@SmallTest -class AppLaunchEventTest { - /** - * Test for IntentStarted. - */ - @Test - fun testIntentStarted() { - var intent = Intent() - val valid = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 1L) - val copy = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 1L) - val noneCopy1 = IntentStarted(/* sequenceId= */1L, intent, /* timestampNs= */ 1L) - val noneCopy2 = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 2L) - val noneCopy3 = IntentStarted(/* sequenceId= */2L, Intent(), /* timestampNs= */ 1L) - - // equals(Object other) - assertThat(valid).isEqualTo(copy) - assertThat(valid).isNotEqualTo(noneCopy1) - assertThat(valid).isNotEqualTo(noneCopy2) - assertThat(valid).isNotEqualTo(noneCopy3) - - // test toString() - val result = valid.toString() - assertThat(result).isEqualTo("IntentStarted{sequenceId=2, intent=Intent { } , timestampNs=1}") - } - - /** - * Test for IntentFailed. - */ - @Test - fun testIntentFailed() { - val valid = IntentFailed(/* sequenceId= */2L) - val copy = IntentFailed(/* sequenceId= */2L) - val noneCopy = IntentFailed(/* sequenceId= */1L) - - // equals(Object other) - assertThat(valid).isEqualTo(copy) - assertThat(valid).isNotEqualTo(noneCopy) - - // test toString() - val result = valid.toString() - assertThat(result).isEqualTo("IntentFailed{sequenceId=2}") - } - - /** - * Test for ActivityLaunched. - */ - @Test - fun testActivityLaunched() { - //var activityRecord = - val valid = ActivityLaunched(/* sequenceId= */2L, "test".toByteArray(), - /* temperature= */ 0) - val copy = ActivityLaunched(/* sequenceId= */2L, "test".toByteArray(), - /* temperature= */ 0) - val noneCopy1 = ActivityLaunched(/* sequenceId= */1L, "test".toByteArray(), - /* temperature= */ 0) - val noneCopy2 = ActivityLaunched(/* sequenceId= */1L, "test".toByteArray(), - /* temperature= */ 1) - val noneCopy3 = ActivityLaunched(/* sequenceId= */1L, "test1".toByteArray(), - /* temperature= */ 0) - - // equals(Object other) - assertThat(valid).isEqualTo(copy) - assertThat(valid).isNotEqualTo(noneCopy1) - assertThat(valid).isNotEqualTo(noneCopy2) - assertThat(valid).isNotEqualTo(noneCopy3) - - // test toString() - val result = valid.toString() - assertThat(result).isEqualTo("ActivityLaunched{sequenceId=2, test, temperature=0}") - } - - - /** - * Test for ActivityLaunchFinished. - */ - @Test - fun testActivityLaunchFinished() { - val valid = ActivityLaunchFinished(/* sequenceId= */2L, "test".toByteArray(), - /* timestampNs= */ 1L) - val copy = ActivityLaunchFinished(/* sequenceId= */2L, "test".toByteArray(), - /* timestampNs= */ 1L) - val noneCopy1 = ActivityLaunchFinished(/* sequenceId= */1L, "test".toByteArray(), - /* timestampNs= */ 1L) - val noneCopy2 = ActivityLaunchFinished(/* sequenceId= */1L, "test".toByteArray(), - /* timestampNs= */ 2L) - val noneCopy3 = ActivityLaunchFinished(/* sequenceId= */2L, "test1".toByteArray(), - /* timestampNs= */ 1L) - - // equals(Object other) - assertThat(valid).isEqualTo(copy) - assertThat(valid).isNotEqualTo(noneCopy1) - assertThat(valid).isNotEqualTo(noneCopy2) - assertThat(valid).isNotEqualTo(noneCopy3) - - // test toString() - val result = valid.toString() - assertThat(result).isEqualTo("ActivityLaunchFinished{sequenceId=2, test, timestampNs=1}") - } - - /** - * Test for ActivityLaunchCancelled. - */ - @Test - fun testActivityLaunchCancelled() { - val valid = ActivityLaunchCancelled(/* sequenceId= */2L, "test".toByteArray()) - val copy = ActivityLaunchCancelled(/* sequenceId= */2L, "test".toByteArray()) - val noneCopy1 = ActivityLaunchCancelled(/* sequenceId= */1L, "test".toByteArray()) - val noneCopy2 = ActivityLaunchCancelled(/* sequenceId= */2L, "test1".toByteArray()) - - // equals(Object other) - assertThat(valid).isEqualTo(copy) - assertThat(valid).isNotEqualTo(noneCopy1) - assertThat(valid).isNotEqualTo(noneCopy2) - - // test toString() - val result = valid.toString() - assertThat(result).isEqualTo("ActivityLaunchCancelled{sequenceId=2, test}") - } - - /** - * Test for ReportFullyDrawn. - */ - @Test - fun testReportFullyDrawn() { - val valid = ReportFullyDrawn(/* sequenceId= */2L, "test".toByteArray(), /* timestampNs= */ 1L) - val copy = ReportFullyDrawn(/* sequenceId= */2L, "test".toByteArray(), /* timestampNs= */ 1L) - val noneCopy1 = ReportFullyDrawn(/* sequenceId= */1L, "test".toByteArray(), - /* timestampNs= */ 1L) - val noneCopy2 = ReportFullyDrawn(/* sequenceId= */1L, "test".toByteArray(), - /* timestampNs= */ 1L) - val noneCopy3 = ReportFullyDrawn(/* sequenceId= */2L, "test1".toByteArray(), - /* timestampNs= */ 1L) - - // equals(Object other) - assertThat(valid).isEqualTo(copy) - assertThat(valid).isNotEqualTo(noneCopy1) - assertThat(valid).isNotEqualTo(noneCopy2) - assertThat(valid).isNotEqualTo(noneCopy3) - - // test toString() - val result = valid.toString() - assertThat(result).isEqualTo("ReportFullyDrawn{sequenceId=2, test, timestampNs=1}") - } -} diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/IIorapIntegrationTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/IIorapIntegrationTest.kt deleted file mode 100644 index 18c249136d05..000000000000 --- a/startop/iorap/tests/src/com/google/android/startop/iorap/IIorapIntegrationTest.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.google.android.startop.iorap - -import android.net.Uri -import android.os.ServiceManager -import androidx.test.filters.FlakyTest -import androidx.test.filters.MediumTest -import org.junit.Test -import org.mockito.Mockito.argThat -import org.mockito.Mockito.eq -import org.mockito.Mockito.inOrder -import org.mockito.Mockito.spy -import org.mockito.Mockito.timeout - -// @Ignore("Test is disabled until iorapd is added to init and there's selinux policies for it") -@MediumTest -@FlakyTest(bugId = 149098310) // Failing on cuttlefish with SecurityException. -class IIorapIntegrationTest { - /** - * @throws ServiceManager.ServiceNotFoundException if iorapd service could not be found - */ - private val iorapService: IIorap by lazy { - // TODO: connect to 'iorapd.stub' which doesn't actually do any work other than reply. - IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")) - - // Use 'adb shell setenforce 0' otherwise this whole test fails, - // because the servicemanager is not allowed to hand out the binder token for iorapd. - - // TODO: implement the selinux policies for iorapd. - } - - // A dummy binder stub implementation is required to use with mockito#spy. - // Mockito overrides the methods at runtime and tracks how methods were invoked. - open class DummyTaskListener : ITaskListener.Stub() { - // Note: make parameters nullable to avoid the kotlin IllegalStateExceptions - // from using the mockito matchers (eq, argThat, etc). - override fun onProgress(requestId: RequestId?, result: TaskResult?) { - } - - override fun onComplete(requestId: RequestId?, result: TaskResult?) { - } - } - - private fun testAnyMethod(func: (RequestId) -> Unit) { - val taskListener = spy(DummyTaskListener())!! - - // FIXME: b/149098310 - return - - try { - iorapService.setTaskListener(taskListener) - // Note: Binder guarantees total order for oneway messages sent to the same binder - // interface, so we don't need any additional blocking here before sending later calls. - - // Every new method call should have a unique request id. - val requestId = RequestId.nextValueForSequence()!! - - // Apply the specific function under test. - func(requestId) - - // Typical mockito behavior is to allow any-order callbacks, but we want to test order. - val inOrder = inOrder(taskListener) - - // The "stub" behavior of iorapd is that every request immediately gets a response of - // BEGAN,ONGOING,COMPLETED - inOrder.verify(taskListener, timeout(100)) - .onProgress(eq(requestId), argThat { it!!.state == TaskResult.STATE_BEGAN }) - inOrder.verify(taskListener, timeout(100)) - .onProgress(eq(requestId), argThat { it!!.state == TaskResult.STATE_ONGOING }) - inOrder.verify(taskListener, timeout(100)) - .onComplete(eq(requestId), argThat { it!!.state == TaskResult.STATE_COMPLETED }) - inOrder.verifyNoMoreInteractions() - } finally { - // iorapService.setTaskListener(null) - // FIXME: null is broken, C++ side sees a non-null object. - } - } - - @Test - fun testOnPackageEvent() { - // FIXME (b/137134253): implement PackageEvent parsing on the C++ side. - // This is currently (silently: b/137135024) failing because IIorap is 'oneway' and the - // C++ PackageEvent un-parceling fails since its not implemented fully. - /* - testAnyMethod { requestId : RequestId -> - iorapService.onPackageEvent(requestId, - PackageEvent.createReplaced( - Uri.parse("https://www.google.com"), "com.fake.package")) - } - */ - } - - @Test - fun testOnAppIntentEvent() { - testAnyMethod { requestId: RequestId -> - iorapService.onAppIntentEvent(requestId, AppIntentEvent.createDefaultIntentChanged( - ActivityInfo("dont care", "dont care"), - ActivityInfo("dont care 2", "dont care 2"))) - } - } - - @Test - fun testOnAppLaunchEvent() { - testAnyMethod { requestId : RequestId -> - iorapService.onAppLaunchEvent(requestId, AppLaunchEvent.IntentFailed(/*sequenceId*/123)) - } - } - - @Test - fun testOnSystemServiceEvent() { - testAnyMethod { requestId: RequestId -> - iorapService.onSystemServiceEvent(requestId, - SystemServiceEvent(SystemServiceEvent.TYPE_START)) - } - } - - @Test - fun testOnSystemServiceUserEvent() { - testAnyMethod { requestId: RequestId -> - iorapService.onSystemServiceUserEvent(requestId, - SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER, 0)) - } - } -} diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt deleted file mode 100644 index 150577a21f5a..000000000000 --- a/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.google.android.startop.iorap - -import android.net.Uri -import android.os.Parcel -import android.os.Parcelable -import androidx.test.filters.SmallTest -import org.junit.Test -import org.junit.runner.RunWith -import com.google.common.truth.Truth.assertThat -import org.junit.runners.Parameterized - -/** - * Basic unit tests to ensure that all of the [Parcelable]s in [com.google.android.startop.iorap] - * have a valid-conforming interface implementation. - */ -@SmallTest -@RunWith(Parameterized::class) -class ParcelablesTest<T : Parcelable>(private val inputData: InputData<T>) { - companion object { - private val initialRequestId = RequestId.nextValueForSequence()!! - - @JvmStatic - @Parameterized.Parameters - fun data() = listOf( - InputData( - newActivityInfo(), - newActivityInfo(), - ActivityInfo("some package", "some other activity")), - InputData( - ActivityHintEvent(ActivityHintEvent.TYPE_COMPLETED, newActivityInfo()), - ActivityHintEvent(ActivityHintEvent.TYPE_COMPLETED, newActivityInfo()), - ActivityHintEvent(ActivityHintEvent.TYPE_POST_COMPLETED, - newActivityInfo())), - InputData( - AppIntentEvent.createDefaultIntentChanged(newActivityInfo(), - newActivityInfoOther()), - AppIntentEvent.createDefaultIntentChanged(newActivityInfo(), - newActivityInfoOther()), - AppIntentEvent.createDefaultIntentChanged(newActivityInfoOther(), - newActivityInfo())), - InputData( - PackageEvent.createReplaced(newUri(), "some package"), - PackageEvent.createReplaced(newUri(), "some package"), - PackageEvent.createReplaced(newUri(), "some other package") - ), - InputData(initialRequestId, cloneRequestId(initialRequestId), - RequestId.nextValueForSequence()), - InputData( - SystemServiceEvent(SystemServiceEvent.TYPE_BOOT_PHASE), - SystemServiceEvent(SystemServiceEvent.TYPE_BOOT_PHASE), - SystemServiceEvent(SystemServiceEvent.TYPE_START)), - InputData( - SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER, 12345), - SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER, 12345), - SystemServiceUserEvent(SystemServiceUserEvent.TYPE_CLEANUP_USER, 12345)), - InputData( - TaskResult(TaskResult.STATE_COMPLETED), - TaskResult(TaskResult.STATE_COMPLETED), - TaskResult(TaskResult.STATE_ONGOING)) - ) - - private fun newActivityInfo(): ActivityInfo { - return ActivityInfo("some package", "some activity") - } - - private fun newActivityInfoOther(): ActivityInfo { - return ActivityInfo("some package 2", "some activity 2") - } - - private fun newUri(): Uri { - return Uri.parse("https://www.google.com") - } - - private fun cloneRequestId(requestId: RequestId): RequestId { - val constructor = requestId::class.java.declaredConstructors[0] - constructor.isAccessible = true - return constructor.newInstance(requestId.requestId) as RequestId - } - } - - /** - * Test for [Object.equals] implementation. - */ - @Test - fun testEquality() { - assertThat(inputData.valid).isEqualTo(inputData.valid) - assertThat(inputData.valid).isEqualTo(inputData.validCopy) - assertThat(inputData.valid).isNotEqualTo(inputData.validOther) - } - - /** - * Test for [Parcelable] implementation. - */ - @Test - fun testParcelRoundTrip() { - // calling writeToParcel and then T::CREATOR.createFromParcel would return the same data. - val assertParcels = { it: T, data: InputData<T> -> - val parcel = Parcel.obtain() - it.writeToParcel(parcel, 0) - parcel.setDataPosition(0) // future reads will see all previous writes. - assertThat(it).isEqualTo(data.createFromParcel(parcel)) - parcel.recycle() - } - - assertParcels(inputData.valid, inputData) - assertParcels(inputData.validCopy, inputData) - assertParcels(inputData.validOther, inputData) - } - - data class InputData<T : Parcelable>(val valid: T, val validCopy: T, val validOther: T) { - val kls = valid.javaClass - init { - assertThat(valid).isNotSameInstanceAs(validCopy) - // Don't use isInstanceOf because of phantom warnings in intellij about Class! - assertThat(validCopy.javaClass).isEqualTo(valid.javaClass) - assertThat(validOther.javaClass).isEqualTo(valid.javaClass) - } - - fun createFromParcel(parcel: Parcel): T { - val field = kls.getDeclaredField("CREATOR") - val creator = field.get(null) as Parcelable.Creator<T> - - return creator.createFromParcel(parcel) - } - } -} diff --git a/startop/scripts/app_startup/analyze_metrics.py b/startop/scripts/app_startup/analyze_metrics.py deleted file mode 100755 index d74d6f68d823..000000000000 --- a/startop/scripts/app_startup/analyze_metrics.py +++ /dev/null @@ -1,457 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -Perform statistical analysis on measurements produced by app_startup_runner.py - -Install: -$> sudo apt-get install python3-scipy - -Usage: -$> ./analyze_metrics.py <filename.csv> [<filename2.csv> ...] -$> ./analyze_metrics.py --help -""" - -import argparse -import csv -import itertools -import os -import subprocess -import sys -import tempfile -from typing import Any, List, Dict, Iterable, TextIO, Tuple - -from scipy import stats as sc -import numpy as np - - -# These CSV columns are considered labels. Everything after them in the same row are metrics. -_LABEL_COLUMNS=['packages', 'readaheads', 'compiler_filters'] -# The metric series with the 'cold' readahead is the baseline. -# All others (warm, jit, etc) are the potential improvements. - -#fixme: this should probably be an option -_BASELINE=('readaheads', 'cold') -# ignore this for some statistic calculations -_IGNORE_PAIR=('readaheads', 'warm') -_PLOT_SUBKEY='readaheads' -_PLOT_GROUPKEY='packages' -_PLOT_DATA_INDEX = 0 -_DELTA=50 -_DELTA2=100 -_PVALUE_THRESHOLD=0.10 -_debug = False # See -d/--debug flag. - -def parse_options(argv: List[str] = None): - """Parse command line arguments and return an argparse Namespace object.""" - parser = argparse.ArgumentParser(description="Perform statistical analysis on measurements produced by app_start_runner.py.") - parser.add_argument('input_files', metavar='file.csv', nargs='+', help='CSV file produced by app_startup_runner.py') - - parser.add_argument('-d', '--debug', dest='debug', action='store_true', help='Add extra debugging output') - parser.add_argument('-os', '--output-samples', dest='output_samples', default='/dev/null', action='store', help='Store CSV for per-sample data') - parser.add_argument('-oc', '--output-comparable', dest='output_comparable', default='/dev/null', action='store', help='Output CSV for comparable against baseline') - parser.add_argument('-ocs', '--output-comparable-significant', dest='output_comparable_significant', default='/dev/null', action='store', help='Output CSV for comparable against baseline (significant only)') - parser.add_argument('-pt', '--pvalue-threshold', dest='pvalue_threshold', type=float, default=_PVALUE_THRESHOLD, action='store') - parser.add_argument('-dt', '--delta-threshold', dest='delta_threshold', type=int, default=_DELTA, action='store') - - return parser.parse_args(argv) - -def _debug_print(*args, **kwargs): - """Print the args to sys.stderr if the --debug/-d flag was passed in.""" - global _debug - if _debug: - print(*args, **kwargs, file=sys.stderr) - -def _expand_gen_repr(args): - new_args_list = [] - for i in args: - # detect iterable objects that do not have their own override of __str__ - if hasattr(i, '__iter__'): - to_str = getattr(i, '__str__') - if to_str.__objclass__ == object: - # the repr for a generator is just type+address, expand it out instead. - new_args_list.append([_expand_gen_repr([j])[0] for j in i]) - continue - # normal case: uses the built-in to-string - new_args_list.append(i) - return new_args_list - -def _debug_print_gen(*args, **kwargs): - """Like _debug_print but will turn any iterable args into a list.""" - if not _debug: - return - - new_args_list = _expand_gen_repr(args) - _debug_print(*new_args_list, **kwargs) - -def read_headers(input_file: TextIO) -> Tuple[List[str], List[str]]: - _debug_print("read_headers for file: ", input_file.name) - csv_reader = csv.reader(input_file) - - label_num_columns = len(_LABEL_COLUMNS) - - try: - header = next(csv_reader) - except StopIteration: - header = None - _debug_print('header', header) - - if not header: - return (None, None) - - labels = header[0:label_num_columns] - data = header[label_num_columns:] - - return (labels, data) - -def read_labels_and_data(input_file: TextIO) -> Iterable[Tuple[List[str], List[int]]]: - _debug_print("print_analysis for file: ", input_file.name) - csv_reader = csv.reader(input_file) - - # Skip the header because it doesn't contain any data. - # To get the header see read_headers function. - try: - header = next(csv_reader) - except StopIteration: - header = None - - label_num_columns = len(_LABEL_COLUMNS) - - for row in csv_reader: - if len(row) > 0 and row[0][0] == ';': - _debug_print("skip comment line", row) - continue - - labels = row[0:label_num_columns] - data = [int(i) for i in row[label_num_columns:]] - -# _debug_print("labels:", labels) -# _debug_print("data:", data) - - yield (labels, data) - -def group_metrics_by_label(it: Iterable[Tuple[List[str], List[int]]]): - prev_labels = None - data_2d = [] - - for label_list, data_list in it: - if prev_labels != label_list: - if prev_labels: -# _debug_print("grouped labels:", prev_labels, "data_2d:", data_2d) - yield (prev_labels, data_2d) - data_2d = [] - - data_2d.append(data_list) - prev_labels = label_list - - if prev_labels: -# _debug_print("grouped labels:", prev_labels, "data_2d:", data_2d) - yield (prev_labels, data_2d) - -def data_to_numpy(it: Iterable[Tuple[List[str], List[List[int]]]]) -> Iterable[Tuple[List[str], Any]]: - for label_list, data_2d in it: - yield (label_list, np.asarray(data_2d, dtype=int)) - -def iterate_columns(np_data_2d): - for col in range(np_data_2d.shape[1]): - col_as_array = np_data_2d[:, col] - yield col_as_array - -def confidence_interval(np_data_2d, percent=0.95): - """ - Given some data [[a,b,c],[d,e,f,]...] - - We assume the same metric is in the column (e.g. [a,d]) - and that data in the rows (e.g. [b,e]) are separate metric values. - - We then calculate the CI for each metric individually returning it as a list of tuples. - """ - arr = [] - for col_2d in iterate_columns(np_data_2d): - mean = col_2d.mean() - sigma = col_2d.std() - - ci = sc.norm.interval(percent, loc=mean, scale=sigma / np.sqrt(len(col_2d))) - arr.append(ci) - - # TODO: This seems to be returning NaN when all the samples have the same exact value - # (e.g. stddev=0, which can trivially happen when sample count = 1). - - return arr - -def print_analysis(it, label_header: List[str], data_header: List[str], output_samples: str): - print(label_header) - - with open(output_samples, "w") as output_file: - - csv_writer = csv.writer(output_file) - csv_writer.writerow(label_header + ['mean', 'std', 'confidence_interval_a', 'confidence_interval_b']) - - for label_list, np_data_2d in it: - print("**********************") - print(label_list) - print() - print(" ", data_header) - # aggregate computation column-wise - print("Mean: ", np_data_2d.mean(axis=0)) - print("Std: ", np_data_2d.std(axis=0)) - print("CI95%:", confidence_interval(np_data_2d)) - print("SEM: ", stats_standard_error_one(np_data_2d, axis=0)) - - #ci = confidence_interval(np_data_2d)[_PLOT_DATA_INDEX] - sem = stats_standard_error_one(np_data_2d, axis=0)[_PLOT_DATA_INDEX] - mean = np_data_2d.mean(axis=0)[_PLOT_DATA_INDEX] - - ci = (mean - sem, mean + sem) - - csv_writer.writerow(label_list + [mean, np_data_2d.std(axis=0)[_PLOT_DATA_INDEX], ci[0], ci[1]]) - -def from_file_group_by_labels(input_file): - (label_header, data_header) = read_headers(input_file) - label_data_iter = read_labels_and_data(input_file) - grouped_iter = group_metrics_by_label(label_data_iter) - grouped_numpy_iter = data_to_numpy(grouped_iter) - - return grouped_numpy_iter, label_header, data_header - -def list_without_index(list, index): - return list[:index] + list[index+1:] - -def group_by_without_baseline_key(grouped_numpy_iter, label_header): - """ - Data is considered comparable if the only difference is the baseline key - (i.e. the readahead is different but the package, compilation filter, etc, are the same). - - Returns iterator that's grouped by the non-baseline labels to an iterator of - (label_list, data_2d). - """ - baseline_index = label_header.index(_BASELINE[0]) - - def get_label_without_baseline(tpl): - label_list, _ = tpl - return list_without_index(label_list, baseline_index) - # [['pkgname', 'compfilter', 'warm'], [data]] - # [['pkgname', 'compfilter', 'cold'], [data2]] - # [['pkgname2', 'compfilter', 'warm'], [data3]] - # - # -> - # ( [['pkgname', 'compfilter', 'warm'], [data]] # ignore baseline label change. - # [['pkgname', 'compfilter', 'cold'], [data2]] ), # split here because the pkgname changed. - # ( [['pkgname2', 'compfilter', 'warm'], [data3]] ) - for group_info, it in itertools.groupby(grouped_numpy_iter, key = get_label_without_baseline): - yield it - - # TODO: replace this messy manual iteration/grouping with pandas - -def iterate_comparable_metrics(without_baseline_iter, label_header): - baseline_index = label_header.index(_BASELINE[0]) - baseline_value = _BASELINE[1] - - _debug_print("iterate comparables") - - def is_baseline_fun(tp): - ll, dat = tp - return ll[baseline_index] == baseline_value - - # iterating here when everything but the baseline key is the same. - for it in without_baseline_iter: - it1, it2 = itertools.tee(it) - - # find all the baseline data. - baseline_filter_it = filter(is_baseline_fun, it1) - - # find non-baseline data. - nonbaseline_filter_it = itertools.filterfalse(is_baseline_fun, it2) - - yield itertools.product(baseline_filter_it, nonbaseline_filter_it) - -def stats_standard_error_one(a, axis): - a_std = a.std(axis=axis, ddof=0) - a_len = a.shape[axis] - - return a_std / np.sqrt(a_len) - -def stats_standard_error(a, b, axis): - a_std = a.std(axis=axis, ddof=0) - b_std = b.std(axis=axis, ddof=0) - - a_len = a.shape[axis] - b_len = b.shape[axis] - - temp1 = a_std*a_std/a_len - temp2 = b_std*b_std/b_len - - return np.sqrt(temp1 + temp2) - -def stats_tvalue(a, b, axis, delta = 0): - a_mean = a.mean(axis=axis) - b_mean = b.mean(axis=axis) - - return (a_mean - b_mean - delta) / stats_standard_error(a, b, axis) - -def stats_pvalue(a, b, axis, delta, left:bool = False): - """ - Single-tailed 2-sample t-test. - - Returns p-value for the null hypothesis: mean(a) - mean(b) >= delta. - :param a: numpy 2d array - :param b: numpy 2d array - :param axis: which axis to do the calculations across - :param delta: test value of mean differences - :param left: if true then use <= delta instead of >= delta - :return: p-value - """ - # implement our own pvalue calculation because the built-in t-test (t,p values) - # only offer delta=0 , e.g. m1-m1 ? 0 - # we are however interested in m1-m2 ? delta - t_value = stats_tvalue(a, b, axis, delta) - - # 2-sample degrees of freedom is using the array sizes - 2. - dof = a.shape[axis] + b.shape[axis] - 2 - - if left: - # left tailed test. e.g. m1-m2 <= delta - return sc.t.cdf(t_value, dof) - else: - # right tailed test. e.g. m1-m2 >= delta - return sc.t.sf(t_value, dof) - # a left+right tailed test is a 2-tail t-test and can be done using ttest_ind for delta=0 - -def print_comparable_analysis(comparable_metrics_iter, label_header, data_header, output_comparable: str, output_comparable_significant: str): - baseline_value = _BASELINE[1] - baseline_index = label_header.index(_BASELINE[0]) - - old_baseline_label_list = None - delta = _DELTA - filter_value = _IGNORE_PAIR[1] - filter_index = label_header.index(_IGNORE_PAIR[0]) - - pvalue_threshold = _PVALUE_THRESHOLD - ci_threshold = (1 - _PVALUE_THRESHOLD) * 100.0 - - with open(output_comparable, "w") as output_file: - - csv_writer = csv.writer(output_file) - csv_writer.writerow(label_header + ['mean', 'mean_diff', 'sem', 'pvalue_2tailed', 'pvalue_gt%d' %(_DELTA), 'pvalue_gt%d' %(_DELTA2)]) - - print("------------------------------------------------------------------") - print("Comparison against the baseline %s = %s" %(_BASELINE, baseline_value)) - print("--- Right-tailed t-test checks if the baseline >= current %s by at least %d" %(_BASELINE[0], delta)) - print() - - global_stats = {'better_than_delta': [], 'better_than_delta_p95': []} - - for nested_it in comparable_metrics_iter: - print("************************") - - better_than_delta = [] - better_than_delta_p95 = [] - - saw_baseline_once = False - - for ((baseline_label_list, baseline_np_data_2d), (rest_label_list, rest_np_data_2d)) in nested_it: - _debug_print("baseline_label_list:", baseline_label_list) - _debug_print("baseline_np_data_2d:", baseline_np_data_2d) - _debug_print("rest_label_list:", rest_label_list) - _debug_print("rest_np_data_2d:", rest_np_data_2d) - - mean_diff = baseline_np_data_2d.mean(axis=0) - rest_np_data_2d.mean(axis=0) - # 2-sample 2-tailed t-test with delta=0 - # e.g. "Is it true that usually the two sample means are different?" - t_statistic, t_pvalue = sc.ttest_ind(baseline_np_data_2d, rest_np_data_2d, axis=0) - - # 2-sample 1-tailed t-test with delta=50 - # e.g. "Is it true that usually the sample means better than 50ms?" - t2 = stats_tvalue(baseline_np_data_2d, rest_np_data_2d, axis=0, delta=delta) - p2 = stats_pvalue(baseline_np_data_2d, rest_np_data_2d, axis=0, delta=delta) - - t2_b = stats_tvalue(baseline_np_data_2d, rest_np_data_2d, axis=0, delta=_DELTA2) - p2_b = stats_pvalue(baseline_np_data_2d, rest_np_data_2d, axis=0, delta=_DELTA2) - - print("%s vs %s" %(rest_label_list, baseline_value)) - print(" ", data_header) - print("Mean Difference: ", mean_diff) - print("T-test (2-tailed) != 0: t=%s, p=%s" %(t_statistic, t_pvalue)) - print("T-test (right-tailed) >= %d: t=%s, p=%s" %(_DELTA, t2, p2)) - print("T-test (right-tailed) >= %d: t=%s, p=%s" %(_DELTA2, t2_b, p2_b)) - - def write_out_values(label_list, *args): - csv_writer.writerow(label_list + [i[_PLOT_DATA_INDEX] for i in args]) - - sem = stats_standard_error(baseline_np_data_2d, rest_np_data_2d, axis=0) - if saw_baseline_once == False: - saw_baseline_once = True - base_sem = stats_standard_error_one(baseline_np_data_2d, axis=0) - write_out_values(baseline_label_list, baseline_np_data_2d.mean(axis=0), [0], base_sem, [None], [None], [None]) - write_out_values(rest_label_list, rest_np_data_2d.mean(axis=0), mean_diff, sem, t_pvalue, p2, p2_b) - - # now do the global statistics aggregation - - if rest_label_list[filter_index] == filter_value: - continue - - if mean_diff > delta: - better_than_delta.append((mean_diff, p2, rest_label_list)) - - if p2 <= pvalue_threshold: - better_than_delta_p95.append((mean_diff, rest_label_list)) - - if better_than_delta: - global_stats['better_than_delta'].append(better_than_delta) - if better_than_delta_p95: - global_stats['better_than_delta_p95'].append(better_than_delta_p95) - - print("------------------------") - print("Global statistics:") - print("//// Rows with %s=%s are ignored here." %_IGNORE_PAIR) - print("- # of results with mean diff better than delta(%d) = %d" %(delta, len(global_stats['better_than_delta']))) - print(" > (meandiff, pvalue, labels)") - for i in global_stats['better_than_delta']: - print(" > %s" %i) - print("- # of results with mean diff better than delta(%d) CI%d%% = %d" %(delta, ci_threshold, len(global_stats['better_than_delta_p95']))) - print(" > (meandiff, labels)") - for i in global_stats['better_than_delta_p95']: - print(" > %s" %i) - -def main(): - global _debug - global _DELTA - global _PVALUE_THRESHOLD - - opts = parse_options() - _debug = opts.debug - _debug_print("parsed options: ", opts) - - _PVALUE_THRESHOLD = opts.pvalue_threshold or _PVALUE_THRESHOLD - - for file_name in opts.input_files: - with open(file_name, 'r') as input_file: - (grouped_numpy_iter, label_header, data_header) = from_file_group_by_labels(input_file) - print_analysis(grouped_numpy_iter, label_header, data_header, opts.output_samples) - - with open(file_name, 'r') as input_file: - (grouped_numpy_iter, label_header, data_header) = from_file_group_by_labels(input_file) - without_baseline_iter = group_by_without_baseline_key(grouped_numpy_iter, label_header) - #_debug_print_gen(without_baseline_iter) - - comparable_metrics_iter = iterate_comparable_metrics(without_baseline_iter, label_header) - print_comparable_analysis(comparable_metrics_iter, label_header, data_header, opts.output_comparable, opts.output_comparable_significant) - - return 0 - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/startop/scripts/app_startup/app_startup_runner.py b/startop/scripts/app_startup/app_startup_runner.py deleted file mode 100755 index 25ee6f7368c8..000000000000 --- a/startop/scripts/app_startup/app_startup_runner.py +++ /dev/null @@ -1,393 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# -# Measure application start-up time by launching applications under various combinations. -# See --help for more details. -# -# -# Sample usage: -# $> ./app_startup_runner.py -p com.google.android.calculator -r warm -r cold -lc 10 -o out.csv -# $> ./analyze_metrics.py out.csv -# -# - -import argparse -import csv -import itertools -import os -import sys -import tempfile -from datetime import timedelta -from typing import Any, Callable, Iterable, List, NamedTuple, TextIO, Tuple, \ - TypeVar, Union, Optional - -# local import -DIR = os.path.abspath(os.path.dirname(__file__)) -sys.path.append(os.path.dirname(DIR)) -import lib.cmd_utils as cmd_utils -import lib.print_utils as print_utils -from app_startup.run_app_with_prefetch import PrefetchAppRunner -import app_startup.lib.args_utils as args_utils -from app_startup.lib.data_frame import DataFrame -from app_startup.lib.perfetto_trace_collector import PerfettoTraceCollector -from iorap.compiler import CompilerType -import iorap.compiler as compiler - -# The following command line options participate in the combinatorial generation. -# All other arguments have a global effect. -_COMBINATORIAL_OPTIONS = ['package', 'readahead', 'compiler_filter', - 'activity', 'trace_duration'] -_TRACING_READAHEADS = ['mlock', 'fadvise'] -_FORWARD_OPTIONS = {'loop_count': '--count'} -_RUN_SCRIPT = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'run_app_with_prefetch.py') - -CollectorPackageInfo = NamedTuple('CollectorPackageInfo', - [('package', str), ('compiler_filter', str)]) -# by 2; systrace starts up slowly. - -_UNLOCK_SCREEN_SCRIPT = os.path.join( - os.path.dirname(os.path.realpath(__file__)), 'unlock_screen') - -RunCommandArgs = NamedTuple('RunCommandArgs', - [('package', str), - ('readahead', str), - ('activity', Optional[str]), - ('compiler_filter', Optional[str]), - ('timeout', Optional[int]), - ('debug', bool), - ('simulate', bool), - ('input', Optional[str]), - ('trace_duration', Optional[timedelta])]) - -# This must be the only mutable global variable. All other global variables are constants to avoid magic literals. -_debug = False # See -d/--debug flag. -_DEBUG_FORCE = None # Ignore -d/--debug if this is not none. -_PERFETTO_TRACE_DURATION_MS = 5000 # milliseconds -_PERFETTO_TRACE_DURATION = timedelta(milliseconds=_PERFETTO_TRACE_DURATION_MS) - -# Type hinting names. -T = TypeVar('T') -NamedTupleMeta = Callable[ - ..., T] # approximation of a (S : NamedTuple<T> where S() == T) metatype. - -def parse_options(argv: List[str] = None): - """Parse command line arguments and return an argparse Namespace object.""" - parser = argparse.ArgumentParser(description="Run one or more Android " - "applications under various " - "settings in order to measure " - "startup time.") - # argparse considers args starting with - and -- optional in --help, even though required=True. - # by using a named argument group --help will clearly say that it's required instead of optional. - required_named = parser.add_argument_group('required named arguments') - required_named.add_argument('-p', '--package', action='append', - dest='packages', - help='package of the application', required=True) - required_named.add_argument('-r', '--readahead', action='append', - dest='readaheads', - help='which readahead mode to use', - choices=('warm', 'cold', 'mlock', 'fadvise'), - required=True) - - # optional arguments - # use a group here to get the required arguments to appear 'above' the optional arguments in help. - optional_named = parser.add_argument_group('optional named arguments') - optional_named.add_argument('-c', '--compiler-filter', action='append', - dest='compiler_filters', - help='which compiler filter to use. if omitted it does not enforce the app\'s compiler filter', - choices=('speed', 'speed-profile', 'quicken')) - optional_named.add_argument('-s', '--simulate', dest='simulate', - action='store_true', - help='Print which commands will run, but don\'t run the apps') - optional_named.add_argument('-d', '--debug', dest='debug', - action='store_true', - help='Add extra debugging output') - optional_named.add_argument('-o', '--output', dest='output', action='store', - help='Write CSV output to file.') - optional_named.add_argument('-t', '--timeout', dest='timeout', action='store', - type=int, default=10, - help='Timeout after this many seconds when executing a single run.') - optional_named.add_argument('-lc', '--loop-count', dest='loop_count', - default=1, type=int, action='store', - help='How many times to loop a single run.') - optional_named.add_argument('-in', '--inodes', dest='inodes', type=str, - action='store', - help='Path to inodes file (system/extras/pagecache/pagecache.py -d inodes)') - optional_named.add_argument('--compiler-trace-duration-ms', - dest='trace_duration', - type=lambda ms_str: timedelta(milliseconds=int(ms_str)), - action='append', - help='The trace duration (milliseconds) in ' - 'compilation') - optional_named.add_argument('--compiler-type', dest='compiler_type', - type=CompilerType, choices=list(CompilerType), - default=CompilerType.DEVICE, - help='The type of compiler.') - - return parser.parse_args(argv) - -def key_to_cmdline_flag(key: str) -> str: - """Convert key into a command line flag, e.g. 'foo-bars' -> '--foo-bar' """ - if key.endswith("s"): - key = key[:-1] - return "--" + key.replace("_", "-") - -def as_run_command(tpl: NamedTuple) -> List[Union[str, Any]]: - """ - Convert a named tuple into a command-line compatible arguments list. - - Example: ABC(1, 2, 3) -> ['--a', 1, '--b', 2, '--c', 3] - """ - args = [] - for key, value in tpl._asdict().items(): - if value is None: - continue - args.append(key_to_cmdline_flag(key)) - args.append(value) - return args - -def run_perfetto_collector(collector_info: CollectorPackageInfo, - timeout: int, - simulate: bool) -> Tuple[bool, TextIO]: - """Run collector to collect prefetching trace. - - Returns: - A tuple of whether the collection succeeds and the generated trace file. - """ - tmp_output_file = tempfile.NamedTemporaryFile() - - collector = PerfettoTraceCollector(package=collector_info.package, - activity=None, - compiler_filter=collector_info.compiler_filter, - timeout=timeout, - simulate=simulate, - trace_duration=_PERFETTO_TRACE_DURATION, - save_destination_file_path=tmp_output_file.name) - result = collector.run() - - return result is not None, tmp_output_file - -def parse_run_script_csv_file(csv_file: TextIO) -> DataFrame: - """Parse a CSV file full of integers into a DataFrame.""" - csv_reader = csv.reader(csv_file) - - try: - header_list = next(csv_reader) - except StopIteration: - header_list = [] - - if not header_list: - return None - - headers = [i for i in header_list] - - d = {} - for row in csv_reader: - header_idx = 0 - - for i in row: - v = i - if i: - v = int(i) - - header_key = headers[header_idx] - l = d.get(header_key, []) - l.append(v) - d[header_key] = l - - header_idx = header_idx + 1 - - return DataFrame(d) - -def build_ri_compiler_argv(inodes_path: str, - perfetto_trace_file: str, - trace_duration: Optional[timedelta] - ) -> str: - argv = ['-i', inodes_path, '--perfetto-trace', - perfetto_trace_file] - - if trace_duration is not None: - argv += ['--duration', str(int(trace_duration.total_seconds() - * PerfettoTraceCollector.MS_PER_SEC))] - - print_utils.debug_print(argv) - return argv - -def execute_run_using_perfetto_trace(collector_info, - run_combos: Iterable[RunCommandArgs], - simulate: bool, - inodes_path: str, - timeout: int, - compiler_type: CompilerType, - requires_trace_collection: bool) -> DataFrame: - """ Executes run based on perfetto trace. """ - if requires_trace_collection: - passed, perfetto_trace_file = run_perfetto_collector(collector_info, - timeout, - simulate) - if not passed: - raise RuntimeError('Cannot run perfetto collector!') - else: - perfetto_trace_file = tempfile.NamedTemporaryFile() - - with perfetto_trace_file: - for combos in run_combos: - if combos.readahead in _TRACING_READAHEADS: - if simulate: - compiler_trace_file = tempfile.NamedTemporaryFile() - else: - ri_compiler_argv = build_ri_compiler_argv(inodes_path, - perfetto_trace_file.name, - combos.trace_duration) - compiler_trace_file = compiler.compile(compiler_type, - inodes_path, - ri_compiler_argv, - combos.package, - combos.activity) - - with compiler_trace_file: - combos = combos._replace(input=compiler_trace_file.name) - print_utils.debug_print(combos) - output = PrefetchAppRunner(**combos._asdict()).run() - else: - print_utils.debug_print(combos) - output = PrefetchAppRunner(**combos._asdict()).run() - - yield DataFrame(dict((x, [y]) for x, y in output)) if output else None - -def execute_run_combos( - grouped_run_combos: Iterable[Tuple[CollectorPackageInfo, Iterable[RunCommandArgs]]], - simulate: bool, - inodes_path: str, - timeout: int, - compiler_type: CompilerType, - requires_trace_collection: bool): - # nothing will work if the screen isn't unlocked first. - cmd_utils.execute_arbitrary_command([_UNLOCK_SCREEN_SCRIPT], - timeout, - simulate=simulate, - shell=False) - - for collector_info, run_combos in grouped_run_combos: - yield from execute_run_using_perfetto_trace(collector_info, - run_combos, - simulate, - inodes_path, - timeout, - compiler_type, - requires_trace_collection) - -def gather_results(commands: Iterable[Tuple[DataFrame]], - key_list: List[str], value_list: List[Tuple[str, ...]]): - print_utils.debug_print("gather_results: key_list = ", key_list) - stringify_none = lambda s: s is None and "<none>" or s - # yield key_list + ["time(ms)"] - for (run_result_list, values) in itertools.zip_longest(commands, value_list): - print_utils.debug_print("run_result_list = ", run_result_list) - print_utils.debug_print("values = ", values) - - if not run_result_list: - continue - - # RunCommandArgs(package='com.whatever', readahead='warm', compiler_filter=None) - # -> {'package':['com.whatever'], 'readahead':['warm'], 'compiler_filter':[None]} - values_dict = {} - for k, v in values._asdict().items(): - if not k in key_list: - continue - values_dict[k] = [stringify_none(v)] - - values_df = DataFrame(values_dict) - # project 'values_df' to be same number of rows as run_result_list. - values_df = values_df.repeat(run_result_list.data_row_len) - - # the results are added as right-hand-side columns onto the existing labels for the table. - values_df.merge_data_columns(run_result_list) - - yield values_df - -def eval_and_save_to_csv(output, annotated_result_values): - printed_header = False - - csv_writer = csv.writer(output) - for row in annotated_result_values: - if not printed_header: - headers = row.headers - csv_writer.writerow(headers) - printed_header = True - # TODO: what about when headers change? - - for data_row in row.data_table: - data_row = [d for d in data_row] - csv_writer.writerow(data_row) - - output.flush() # see the output live. - -def coerce_to_list(opts: dict): - """Tranform values of the dictionary to list. - For example: - 1 -> [1], None -> [None], [1,2,3] -> [1,2,3] - [[1],[2]] -> [[1],[2]], {1:1, 2:2} -> [{1:1, 2:2}] - """ - result = {} - for key in opts: - val = opts[key] - result[key] = val if issubclass(type(val), list) else [val] - return result - -def main(): - global _debug - - opts = parse_options() - _debug = opts.debug - if _DEBUG_FORCE is not None: - _debug = _DEBUG_FORCE - - print_utils.DEBUG = _debug - cmd_utils.SIMULATE = opts.simulate - - print_utils.debug_print("parsed options: ", opts) - - output_file = opts.output and open(opts.output, 'w') or sys.stdout - - combos = lambda: args_utils.generate_run_combinations( - RunCommandArgs, - coerce_to_list(vars(opts)), - opts.loop_count) - print_utils.debug_print_gen("run combinations: ", combos()) - - grouped_combos = lambda: args_utils.generate_group_run_combinations(combos(), - CollectorPackageInfo) - - print_utils.debug_print_gen("grouped run combinations: ", grouped_combos()) - requires_trace_collection = any(i in _TRACING_READAHEADS for i in opts.readaheads) - exec = execute_run_combos(grouped_combos(), - opts.simulate, - opts.inodes, - opts.timeout, - opts.compiler_type, - requires_trace_collection) - - results = gather_results(exec, _COMBINATORIAL_OPTIONS, combos()) - - eval_and_save_to_csv(output_file, results) - - return 1 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/startop/scripts/app_startup/app_startup_runner_test.py b/startop/scripts/app_startup/app_startup_runner_test.py deleted file mode 100755 index 0c2bbea04f6a..000000000000 --- a/startop/scripts/app_startup/app_startup_runner_test.py +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -""" -Unit tests for the app_startup_runner.py script. - -Install: - $> sudo apt-get install python3-pytest ## OR - $> pip install -U pytest -See also https://docs.pytest.org/en/latest/getting-started.html - -Usage: - $> ./app_startup_runner_test.py - $> pytest app_startup_runner_test.py - $> python -m pytest app_startup_runner_test.py - -See also https://docs.pytest.org/en/latest/usage.html -""" - -import io -import shlex -import sys -import typing -# global imports -from contextlib import contextmanager - -# local imports -import app_startup_runner as asr -# pip imports -import pytest - -# -# Argument Parsing Helpers -# - -@contextmanager -def ignore_stdout_stderr(): - """Ignore stdout/stderr output for duration of this context.""" - old_stdout = sys.stdout - old_stderr = sys.stderr - sys.stdout = io.StringIO() - sys.stderr = io.StringIO() - try: - yield - finally: - sys.stdout = old_stdout - sys.stderr = old_stderr - -@contextmanager -def argparse_bad_argument(msg): - """ - Assert that a SystemExit is raised when executing this context. - If the assertion fails, print the message 'msg'. - """ - with pytest.raises(SystemExit, message=msg): - with ignore_stdout_stderr(): - yield - -def assert_bad_argument(args, msg): - """ - Assert that the command line arguments in 'args' are malformed. - Prints 'msg' if the assertion fails. - """ - with argparse_bad_argument(msg): - parse_args(args) - -def parse_args(args): - """ - :param args: command-line like arguments as a single string - :return: dictionary of parsed key/values - """ - # "-a b -c d" => ['-a', 'b', '-c', 'd'] - return vars(asr.parse_options(shlex.split(args))) - -def default_dict_for_parsed_args(**kwargs): - """ - # Combine it with all of the "optional" parameters' default values. - """ - d = {'compiler_filters': None, 'simulate': False, 'debug': False, - 'output': None, 'timeout': 10, 'loop_count': 1, 'inodes': None, - 'trace_duration': None, 'compiler_type': asr.CompilerType.DEVICE} - d.update(kwargs) - return d - -def default_mock_dict_for_parsed_args(include_optional=True, **kwargs): - """ - Combine default dict with all optional parameters with some mock required parameters. - """ - d = {'packages': ['com.fake.package'], 'readaheads': ['warm']} - if include_optional: - d.update(default_dict_for_parsed_args()) - d.update(kwargs) - return d - -def parse_optional_args(str): - """ - Parse an argument string which already includes all the required arguments - in default_mock_dict_for_parsed_args. - """ - req = "--package com.fake.package --readahead warm" - return parse_args("%s %s" % (req, str)) - -def test_argparse(): - # missing arguments - assert_bad_argument("", "-p and -r are required") - assert_bad_argument("-r warm", "-p is required") - assert_bad_argument("--readahead warm", "-p is required") - assert_bad_argument("-p com.fake.package", "-r is required") - assert_bad_argument("--package com.fake.package", "-r is required") - - # required arguments are parsed correctly - ad = default_dict_for_parsed_args # assert dict - - assert parse_args("--package xyz --readahead warm") == ad(packages=['xyz'], - readaheads=['warm']) - assert parse_args("-p xyz -r warm") == ad(packages=['xyz'], - readaheads=['warm']) - - assert parse_args("-p xyz -r warm -s") == ad(packages=['xyz'], - readaheads=['warm'], - simulate=True) - assert parse_args("-p xyz -r warm --simulate") == ad(packages=['xyz'], - readaheads=['warm'], - simulate=True) - - # optional arguments are parsed correctly. - mad = default_mock_dict_for_parsed_args # mock assert dict - assert parse_optional_args("--output filename.csv") == mad( - output='filename.csv') - assert parse_optional_args("-o filename.csv") == mad(output='filename.csv') - - assert parse_optional_args("--timeout 123") == mad(timeout=123) - assert parse_optional_args("-t 456") == mad(timeout=456) - - assert parse_optional_args("--loop-count 123") == mad(loop_count=123) - assert parse_optional_args("-lc 456") == mad(loop_count=456) - - assert parse_optional_args("--inodes bar") == mad(inodes="bar") - assert parse_optional_args("-in baz") == mad(inodes="baz") - - - -def test_key_to_cmdline_flag(): - assert asr.key_to_cmdline_flag("abc") == "--abc" - assert asr.key_to_cmdline_flag("foos") == "--foo" - assert asr.key_to_cmdline_flag("ba_r") == "--ba-r" - assert asr.key_to_cmdline_flag("ba_zs") == "--ba-z" - -def test_parse_run_script_csv_file(): - # empty file -> empty list - f = io.StringIO("") - assert asr.parse_run_script_csv_file(f) == None - - # common case - f = io.StringIO("TotalTime_ms,Displayed_ms\n1,2") - df = asr.DataFrame({'TotalTime_ms': [1], 'Displayed_ms': [2]}) - - pf = asr.parse_run_script_csv_file(f) - assert pf == df - -if __name__ == '__main__': - pytest.main() diff --git a/startop/scripts/app_startup/force_compiler_filter b/startop/scripts/app_startup/force_compiler_filter deleted file mode 100755 index 08f983d92bea..000000000000 --- a/startop/scripts/app_startup/force_compiler_filter +++ /dev/null @@ -1,143 +0,0 @@ -#!/bin/bash -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# Forces an application APK to be compiled (by ART's dex2oat) -# with a specific compiler filter. -# -# Example usage: -# $> ./force_compiler_filter -p com.google.android.apps.maps -c speed-profile -# -# (The application may be started/stopped as a side effect) -# - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -source "$DIR/lib/common" - -usage() { - cat <<EOF -Usage: $(basename $0) [OPTION]... - - Required: - -p, --package package of the app to recompile - -c, --compiler-filter override the compiler filter if set (default none) - valid options are listed by: adb shell cmd package, under compile -m - - Optional: - -a, --activity activity of the app to recompile - -h, --help usage information (this) - -v, --verbose enable extra verbose printing - -w, --wait_time how long to wait for app startup (default 10) in seconds -EOF -} - -wait_time="10" # seconds - -parse_arguments() { - while [[ $# -gt 0 ]]; do - case "$1" in - -a|--activity) - activity="$2" - shift - ;; - -h|--help) - usage - exit 0 - ;; - -p|--package) - package="$2" - shift - ;; - -w|--wait_time) - wait_time="$2" - shift - ;; - -c|--compiler-filter) - compiler_filter="$2" - shift - ;; - -v|--verbose) - verbose="y" - ;; - esac - shift - done - - if [[ -z "$compiler_filter" ]]; then - echo "Missing required --compiler-filter" >&2 - echo "" - usage - exit 1 - fi - if [[ -z "$package" ]]; then - echo "Missing required --package" >&2 - echo "" - usage - exit 1 - fi - - if [[ "$activity" == "" ]]; then - activity="$(get_activity_name "$package")" - if [[ "$activity" == "" ]]; then - echo "Activity name could not be found, invalid package name?" 1>&2 - exit 1 - else - verbose_print "Activity name inferred: " "$activity" - fi - fi -} - -force_package_compilation() { - local arg_compiler_filter="$1" - local arg_package="$2" - - if [[ $arg_compiler_filter == speed-profile ]]; then - # Force the running app to dump its profiles to disk. - remote_pkill "$arg_package" -SIGUSR1 - sleep 1 # give some time for above to complete. - fi - - adb shell cmd package compile -m "$arg_compiler_filter" -f "$arg_package" -} - -main() { - parse_arguments "$@" - - if [[ $compiler_filter == speed-profile ]]; then - # screen needs to be unlocked in order to run an app - "$DIR"/unlock_screen - - local output=$("$DIR"/launch_application "$package" "$activity") - if [[ $? -ne 0 ]]; then - echo "launching application failed" >&2 - exit 1 - fi - - verbose_print "$output" - # give some time for app startup to complete. - # this is supposed to be an upper bound for measuring startup time. - sleep "$wait_time" - fi - - force_package_compilation "$compiler_filter" "$package" - - # kill the application to ensure next time it's started, - # it picks up the correct compilation filter. - adb shell am force-stop "$package" - remote_pkill "$package" -} - -main "$@" diff --git a/startop/scripts/app_startup/launch_application b/startop/scripts/app_startup/launch_application deleted file mode 100755 index 6704a5a97aa0..000000000000 --- a/startop/scripts/app_startup/launch_application +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -source "$DIR/lib/common" - -launch_application_usage() { - cat <<EOF -Usage: $(basename $0) <package> <activity> - - Positional Arguments: - <package> package of the app to test - <activity> activity to use - - Named Arguments: - -h, --help usage information (this) -EOF -} - -launch_application() { - local package="$1" - local activity="$2" - - # if there's any $s inside of the activity name, it needs to be escaped to \$. - # example '.app.honeycomb.Shell$HomeActivity' - # if the $ is not escaped, adb shell will try to evaluate $HomeActivity to a variable. - activity=${activity//\$/\\$} - - adb shell am start -S -W "$package"/"$activity" - - # pipe this into 'parse_metrics' to parse the output. -} - -if [[ $# -lt 2 ]]; then - launch_application_usage - exit 1 -fi - -launch_application "$@" diff --git a/startop/scripts/app_startup/lib/adb_utils.py b/startop/scripts/app_startup/lib/adb_utils.py deleted file mode 100644 index 3cebc9a97a50..000000000000 --- a/startop/scripts/app_startup/lib/adb_utils.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. - -"""Helper util libraries for calling adb command line.""" - -import datetime -import os -import re -import sys -import time -from typing import Optional - -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname( - os.path.abspath(__file__))))) -import lib.cmd_utils as cmd_utils -import lib.logcat_utils as logcat_utils - - -def logcat_save_timestamp() -> str: - """Gets the current logcat timestamp. - - Returns: - A string of timestamp. - """ - _, output = cmd_utils.run_adb_shell_command( - "date -u +\'%Y-%m-%d %H:%M:%S.%N\'") - return output - -def vm_drop_cache(): - """Free pagecache and slab object.""" - cmd_utils.run_adb_shell_command('echo 3 > /proc/sys/vm/drop_caches') - # Sleep a little bit to provide enough time for cache cleanup. - time.sleep(1) - -def root(): - """Roots adb and successive adb commands will run under root.""" - cmd_utils.run_shell_command('adb root') - -def disable_selinux(): - """Disables selinux setting.""" - _, output = cmd_utils.run_adb_shell_command('getenforce') - if output == 'Permissive': - return - - print('Disable selinux permissions and restart framework.') - cmd_utils.run_adb_shell_command('setenforce 0') - cmd_utils.run_adb_shell_command('stop') - cmd_utils.run_adb_shell_command('start') - cmd_utils.run_shell_command('adb wait-for-device') - -def pkill(procname: str): - """Kills a process on device specified by the substring pattern in procname""" - _, pids = cmd_utils.run_shell_command('adb shell ps | grep "{}" | ' - 'awk \'{{print $2;}}\''. - format(procname)) - - for pid in pids.split('\n'): - pid = pid.strip() - if pid: - passed,_ = cmd_utils.run_adb_shell_command('kill {}'.format(pid)) - time.sleep(1) - -def parse_time_to_milliseconds(time: str) -> int: - """Parses the time string to milliseconds.""" - # Example: +1s56ms, +56ms - regex = r'\+((?P<second>\d+?)s)?(?P<millisecond>\d+?)ms' - result = re.search(regex, time) - second = 0 - if result.group('second'): - second = int(result.group('second')) - ms = int(result.group('millisecond')) - return second * 1000 + ms - -def blocking_wait_for_logcat_displayed_time(timestamp: datetime.datetime, - package: str, - timeout: int) -> Optional[int]: - """Parses the displayed time in the logcat. - - Returns: - the displayed time. - """ - pattern = re.compile('.*ActivityTaskManager: Displayed {}.*'.format(package)) - # 2019-07-02 22:28:34.469453349 -> 2019-07-02 22:28:34.469453 - timestamp = datetime.datetime.strptime(timestamp[:-3], - '%Y-%m-%d %H:%M:%S.%f') - timeout_dt = timestamp + datetime.timedelta(0, timeout) - # 2019-07-01 14:54:21.946 27365 27392 I ActivityTaskManager: - # Displayed com.android.settings/.Settings: +927ms - result = logcat_utils.blocking_wait_for_logcat_pattern(timestamp, - pattern, - timeout_dt) - if not result or not '+' in result: - return None - displayed_time = result[result.rfind('+'):] - - return parse_time_to_milliseconds(displayed_time) - -def delete_file_on_device(file_path: str) -> None: - """ Deletes a file on the device. """ - cmd_utils.run_adb_shell_command( - "[[ -f '{file_path}' ]] && rm -f '{file_path}' || " - "exit 0".format(file_path=file_path)) - -def set_prop(property: str, value: str) -> None: - """ Sets property using adb shell. """ - cmd_utils.run_adb_shell_command('setprop "{property}" "{value}"'.format( - property=property, value=value)) - -def pull_file(device_file_path: str, output_file_path: str) -> None: - """ Pulls file from device to output """ - cmd_utils.run_shell_command('adb pull "{device_file_path}" "{output_file_path}"'. - format(device_file_path=device_file_path, - output_file_path=output_file_path)) diff --git a/startop/scripts/app_startup/lib/adb_utils_test.py b/startop/scripts/app_startup/lib/adb_utils_test.py deleted file mode 100644 index e590fed568e3..000000000000 --- a/startop/scripts/app_startup/lib/adb_utils_test.py +++ /dev/null @@ -1,16 +0,0 @@ -import adb_utils - -# pip imports -import pytest - -def test_parse_time_to_milliseconds(): - # Act - result1 = adb_utils.parse_time_to_milliseconds('+1s7ms') - result2 = adb_utils.parse_time_to_milliseconds('+523ms') - - # Assert - assert result1 == 1007 - assert result2 == 523 - -if __name__ == '__main__': - pytest.main() diff --git a/startop/scripts/app_startup/lib/app_runner.py b/startop/scripts/app_startup/lib/app_runner.py deleted file mode 100644 index 78873fa51ab5..000000000000 --- a/startop/scripts/app_startup/lib/app_runner.py +++ /dev/null @@ -1,266 +0,0 @@ -# Copyright 2019, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Class to run an app.""" -import os -import sys -from typing import Optional, List, Tuple - -# local import -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname( - os.path.abspath(__file__))))) - -import app_startup.lib.adb_utils as adb_utils -import lib.cmd_utils as cmd_utils -import lib.print_utils as print_utils - -class AppRunnerListener(object): - """Interface for lisenter of AppRunner. """ - - def preprocess(self) -> None: - """Preprocess callback to initialized before the app is running. """ - pass - - def postprocess(self, pre_launch_timestamp: str) -> None: - """Postprocess callback to cleanup after the app is running. - - param: - 'pre_launch_timestamp': indicates the timestamp when the app is - launching.. """ - pass - - def metrics_selector(self, am_start_output: str, - pre_launch_timestamp: str) -> None: - """A metrics selection callback that waits for the desired metrics to - show up in logcat. - params: - 'am_start_output': indicates the output of app startup. - 'pre_launch_timestamp': indicates the timestamp when the app is - launching. - returns: - a string in the format of "<metric>=<value>\n<metric>=<value>\n..." - for further parsing. For example "TotalTime=123\nDisplayedTime=121". - Return an empty string if no metrics need to be parsed further. - """ - pass - -class AppRunner(object): - """ Class to run an app. """ - # static variables - DIR = os.path.abspath(os.path.dirname(__file__)) - APP_STARTUP_DIR = os.path.dirname(DIR) - IORAP_COMMON_BASH_SCRIPT = os.path.realpath(os.path.join(DIR, - '../../iorap/common')) - DEFAULT_TIMEOUT = 30 # seconds - - def __init__(self, - package: str, - activity: Optional[str], - compiler_filter: Optional[str], - timeout: Optional[int], - simulate: bool): - self.package = package - self.simulate = simulate - - # If the argument activity is None, try to set it. - self.activity = activity - if self.simulate: - self.activity = 'act' - if self.activity is None: - self.activity = AppRunner.get_activity(self.package) - - self.compiler_filter = compiler_filter - self.timeout = timeout if timeout else AppRunner.DEFAULT_TIMEOUT - - self.listeners = [] - - def add_callbacks(self, listener: AppRunnerListener): - self.listeners.append(listener) - - def remove_callbacks(self, listener: AppRunnerListener): - self.listeners.remove(listener) - - @staticmethod - def get_activity(package: str) -> str: - """ Tries to set the activity based on the package. """ - passed, activity = cmd_utils.run_shell_func( - AppRunner.IORAP_COMMON_BASH_SCRIPT, - 'get_activity_name', - [package]) - - if not passed or not activity: - raise ValueError( - 'Activity name could not be found, invalid package name?!') - - return activity - - def configure_compiler_filter(self) -> bool: - """Configures compiler filter (e.g. speed). - - Returns: - A bool indicates whether configure of compiler filer succeeds or not. - """ - if not self.compiler_filter: - print_utils.debug_print('No --compiler-filter specified, don\'t' - ' need to force it.') - return True - - passed, current_compiler_filter_info = \ - cmd_utils.run_shell_command( - '{} --package {}'.format(os.path.join(AppRunner.APP_STARTUP_DIR, - 'query_compiler_filter.py'), - self.package)) - - if passed != 0: - return passed - - # TODO: call query_compiler_filter directly as a python function instead of - # these shell calls. - current_compiler_filter, current_reason, current_isa = \ - current_compiler_filter_info.split(' ') - print_utils.debug_print('Compiler Filter={} Reason={} Isa={}'.format( - current_compiler_filter, current_reason, current_isa)) - - # Don't trust reasons that aren't 'unknown' because that means - # we didn't manually force the compilation filter. - # (e.g. if any automatic system-triggered compilations are not unknown). - if current_reason != 'unknown' or \ - current_compiler_filter != self.compiler_filter: - passed, _ = adb_utils.run_shell_command('{}/force_compiler_filter ' - '--compiler-filter "{}" ' - '--package "{}"' - ' --activity "{}'. - format(AppRunner.APP_STARTUP_DIR, - self.compiler_filter, - self.package, - self.activity)) - else: - adb_utils.debug_print('Queried compiler-filter matched requested ' - 'compiler-filter, skip forcing.') - passed = False - return passed - - def run(self) -> Optional[List[Tuple[str]]]: - """Runs an app. - - Returns: - A list of (metric, value) tuples. - """ - print_utils.debug_print('==========================================') - print_utils.debug_print('===== START =====') - print_utils.debug_print('==========================================') - # Run the preprocess. - for listener in self.listeners: - listener.preprocess() - - # Ensure the APK is currently compiled with whatever we passed in - # via --compiler-filter. - # No-op if this option was not passed in. - if not self.configure_compiler_filter(): - print_utils.error_print('Compiler filter configuration failed!') - return None - - pre_launch_timestamp = adb_utils.logcat_save_timestamp() - # Launch the app. - results = self.launch_app(pre_launch_timestamp) - - # Run the postprocess. - for listener in self.listeners: - listener.postprocess(pre_launch_timestamp) - - return results - - def launch_app(self, pre_launch_timestamp: str) -> Optional[List[Tuple[str]]]: - """ Launches the app. - - Returns: - A list of (metric, value) tuples. - """ - print_utils.debug_print('Running with timeout {}'.format(self.timeout)) - - passed, am_start_output = cmd_utils.run_shell_command('timeout {timeout} ' - '"{DIR}/launch_application" ' - '"{package}" ' - '"{activity}"'. - format(timeout=self.timeout, - DIR=AppRunner.APP_STARTUP_DIR, - package=self.package, - activity=self.activity)) - if not passed and not self.simulate: - return None - - return self.wait_for_app_finish(pre_launch_timestamp, am_start_output) - - def wait_for_app_finish(self, - pre_launch_timestamp: str, - am_start_output: str) -> Optional[List[Tuple[str]]]: - """ Wait for app finish and all metrics are shown in logcat. - - Returns: - A list of (metric, value) tuples. - """ - if self.simulate: - return [('TotalTime', '123')] - - ret = [] - for listener in self.listeners: - output = listener.metrics_selector(am_start_output, - pre_launch_timestamp) - ret = ret + AppRunner.parse_metrics_output(output) - - return ret - - @staticmethod - def parse_metrics_output(input: str) -> List[ - Tuple[str, str, str]]: - """Parses output of app startup to metrics and corresponding values. - - It converts 'a=b\nc=d\ne=f\n...' into '[(a,b,''),(c,d,''),(e,f,'')]' - - Returns: - A list of tuples that including metric name, metric value and rest info. - """ - all_metrics = [] - for line in input.split('\n'): - if not line: - continue - splits = line.split('=') - if len(splits) < 2: - print_utils.error_print('Bad line "{}"'.format(line)) - continue - metric_name = splits[0] - metric_value = splits[1] - rest = splits[2] if len(splits) > 2 else '' - if rest: - print_utils.error_print('Corrupt line "{}"'.format(line)) - print_utils.debug_print('metric: "{metric_name}", ' - 'value: "{metric_value}" '. - format(metric_name=metric_name, - metric_value=metric_value)) - - all_metrics.append((metric_name, metric_value)) - return all_metrics - - @staticmethod - def parse_total_time( am_start_output: str) -> Optional[str]: - """Parses the total time from 'adb shell am start pkg' output. - - Returns: - the total time of app startup. - """ - for line in am_start_output.split('\n'): - if 'TotalTime:' in line: - return line[len('TotalTime:'):].strip() - return None - diff --git a/startop/scripts/app_startup/lib/app_runner_test.py b/startop/scripts/app_startup/lib/app_runner_test.py deleted file mode 100644 index 33d233b03aab..000000000000 --- a/startop/scripts/app_startup/lib/app_runner_test.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright 2019, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -"""Unit tests for the AppRunner.""" -import os -import sys -from pathlib import Path - -from app_runner import AppRunner, AppRunnerListener -from mock import Mock, call, patch - -# The path is "frameworks/base/startop/scripts/" -sys.path.append(Path(os.path.realpath(__file__)).parents[2]) -import lib.cmd_utils as cmd_utils - -class AppRunnerTestListener(AppRunnerListener): - def preprocess(self) -> None: - cmd_utils.run_shell_command('pre'), - - def postprocess(self, pre_launch_timestamp: str) -> None: - cmd_utils.run_shell_command('post'), - - def metrics_selector(self, am_start_output: str, - pre_launch_timestamp: str) -> None: - return 'TotalTime=123\n' - -RUNNER = AppRunner(package='music', - activity='MainActivity', - compiler_filter='speed', - timeout=None, - simulate=False) - - - -def test_configure_compiler_filter(): - with patch('lib.cmd_utils.run_shell_command', - new_callable=Mock) as mock_run_shell_command: - mock_run_shell_command.return_value = (True, 'speed arm64 kUpToDate') - - RUNNER.configure_compiler_filter() - - calls = [call(os.path.realpath( - os.path.join(RUNNER.DIR, - '../query_compiler_filter.py')) + ' --package music')] - mock_run_shell_command.assert_has_calls(calls) - -def test_parse_metrics_output(): - input = 'a1=b1\nc1=d1\ne1=f1' - ret = RUNNER.parse_metrics_output(input) - - assert ret == [('a1', 'b1'), ('c1', 'd1'), ('e1', 'f1')] - -def _mocked_run_shell_command(*args, **kwargs): - if args[0] == 'adb shell "date -u +\'%Y-%m-%d %H:%M:%S.%N\'"': - return (True, "2019-07-02 23:20:06.972674825") - elif args[0] == 'adb shell ps | grep "music" | awk \'{print $2;}\'': - return (True, '9999') - else: - return (True, 'a1=b1\nc1=d1=d2\ne1=f1') - -@patch('app_startup.lib.adb_utils.blocking_wait_for_logcat_displayed_time') -@patch('lib.cmd_utils.run_shell_command') -def test_run(mock_run_shell_command, - mock_blocking_wait_for_logcat_displayed_time): - mock_run_shell_command.side_effect = _mocked_run_shell_command - mock_blocking_wait_for_logcat_displayed_time.return_value = 123 - - test_listener = AppRunnerTestListener() - RUNNER.add_callbacks(test_listener) - - result = RUNNER.run() - - RUNNER.remove_callbacks(test_listener) - - calls = [call('pre'), - call(os.path.realpath( - os.path.join(RUNNER.DIR, - '../query_compiler_filter.py')) + - ' --package music'), - call('adb shell "date -u +\'%Y-%m-%d %H:%M:%S.%N\'"'), - call( - 'timeout {timeout} "{DIR}/launch_application" "{package}" "{activity}"' - .format(timeout=30, - DIR=os.path.realpath(os.path.dirname(RUNNER.DIR)), - package='music', - activity='MainActivity', - timestamp='2019-07-02 23:20:06.972674825')), - call('post') - ] - mock_run_shell_command.assert_has_calls(calls) - assert result == [('TotalTime', '123')] - assert len(RUNNER.listeners) == 0
\ No newline at end of file diff --git a/startop/scripts/app_startup/lib/args_utils.py b/startop/scripts/app_startup/lib/args_utils.py deleted file mode 100644 index 080f3b53157b..000000000000 --- a/startop/scripts/app_startup/lib/args_utils.py +++ /dev/null @@ -1,77 +0,0 @@ -import itertools -import os -import sys -from typing import Any, Callable, Dict, Iterable, List, NamedTuple, Tuple, \ - TypeVar, Optional - -# local import -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname( - os.path.abspath(__file__))))) -import lib.print_utils as print_utils - -T = TypeVar('T') -NamedTupleMeta = Callable[ - ..., T] # approximation of a (S : NamedTuple<T> where S() == T) metatype. -FilterFuncType = Callable[[NamedTuple], bool] - -def dict_lookup_any_key(dictionary: dict, *keys: List[Any]): - for k in keys: - if k in dictionary: - return dictionary[k] - - - print_utils.debug_print("None of the keys {} were in the dictionary".format( - keys)) - return [None] - -def generate_run_combinations(named_tuple: NamedTupleMeta[T], - opts_dict: Dict[str, List[Optional[object]]], - loop_count: int = 1) -> Iterable[T]: - """ - Create all possible combinations given the values in opts_dict[named_tuple._fields]. - - :type T: type annotation for the named_tuple type. - :param named_tuple: named tuple type, whose fields are used to make combinations for - :param opts_dict: dictionary of keys to value list. keys correspond to the named_tuple fields. - :param loop_count: number of repetitions. - :return: an iterable over named_tuple instances. - """ - combinations_list = [] - for k in named_tuple._fields: - # the key can be either singular or plural , e.g. 'package' or 'packages' - val = dict_lookup_any_key(opts_dict, k, k + "s") - - # treat {'x': None} key value pairs as if it was [None] - # otherwise itertools.product throws an exception about not being able to iterate None. - combinations_list.append(val or [None]) - - print_utils.debug_print("opts_dict: ", opts_dict) - print_utils.debug_print_nd("named_tuple: ", named_tuple) - print_utils.debug_print("combinations_list: ", combinations_list) - - for i in range(loop_count): - for combo in itertools.product(*combinations_list): - yield named_tuple(*combo) - -def filter_run_combinations(named_tuple: NamedTuple, - filters: List[FilterFuncType]) -> bool: - for filter in filters: - if filter(named_tuple): - return False - return True - -def generate_group_run_combinations(run_combinations: Iterable[NamedTuple], - dst_nt: NamedTupleMeta[T]) \ - -> Iterable[Tuple[T, Iterable[NamedTuple]]]: - def group_by_keys(src_nt): - src_d = src_nt._asdict() - # now remove the keys that aren't legal in dst. - for illegal_key in set(src_d.keys()) - set(dst_nt._fields): - if illegal_key in src_d: - del src_d[illegal_key] - - return dst_nt(**src_d) - - for args_list_it in itertools.groupby(run_combinations, group_by_keys): - (group_key_value, args_it) = args_list_it - yield (group_key_value, args_it) diff --git a/startop/scripts/app_startup/lib/args_utils_test.py b/startop/scripts/app_startup/lib/args_utils_test.py deleted file mode 100644 index 4b7e0fa20627..000000000000 --- a/startop/scripts/app_startup/lib/args_utils_test.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -"""Unit tests for the args_utils.py script.""" - -import typing - -import args_utils - -def generate_run_combinations(*args): - # expand out the generator values so that assert x == y works properly. - return [i for i in args_utils.generate_run_combinations(*args)] - -def test_generate_run_combinations(): - blank_nd = typing.NamedTuple('Blank') - assert generate_run_combinations(blank_nd, {}, 1) == [()], "empty" - assert generate_run_combinations(blank_nd, {'a': ['a1', 'a2']}) == [ - ()], "empty filter" - a_nd = typing.NamedTuple('A', [('a', str)]) - assert generate_run_combinations(a_nd, {'a': None}) == [(None,)], "None" - assert generate_run_combinations(a_nd, {'a': ['a1', 'a2']}) == [('a1',), ( - 'a2',)], "one item" - assert generate_run_combinations(a_nd, - {'a': ['a1', 'a2'], 'b': ['b1', 'b2']}) == [ - ('a1',), ('a2',)], \ - "one item filter" - assert generate_run_combinations(a_nd, {'a': ['a1', 'a2']}, 2) == [('a1',), ( - 'a2',), ('a1',), ('a2',)], "one item" - ab_nd = typing.NamedTuple('AB', [('a', str), ('b', str)]) - assert generate_run_combinations(ab_nd, - {'a': ['a1', 'a2'], - 'b': ['b1', 'b2']}) == [ab_nd('a1', 'b1'), - ab_nd('a1', 'b2'), - ab_nd('a2', 'b1'), - ab_nd('a2', 'b2')], \ - "two items" - - assert generate_run_combinations(ab_nd, - {'as': ['a1', 'a2'], - 'bs': ['b1', 'b2']}) == [ab_nd('a1', 'b1'), - ab_nd('a1', 'b2'), - ab_nd('a2', 'b1'), - ab_nd('a2', 'b2')], \ - "two items plural" diff --git a/startop/scripts/app_startup/lib/common b/startop/scripts/app_startup/lib/common deleted file mode 100755 index bedaa1e10288..000000000000 --- a/startop/scripts/app_startup/lib/common +++ /dev/null @@ -1,198 +0,0 @@ -#!/bin/bash -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -if [[ -z $ANDROID_BUILD_TOP ]]; then - echo "Please run source build/envsetup.sh first" >&2 - exit 1 -fi - -source $ANDROID_BUILD_TOP/build/envsetup.sh - -verbose_print() { - if [[ "$verbose" == "y" ]]; then - echo "$@" >&2 - fi -} - -remote_pidof() { - local procname="$1" - adb shell ps | grep "$procname" | awk '{print $2;}' -} - -remote_pkill() { - local procname="$1" - shift - - local the_pids=$(remote_pidof "$procname") - local pid - - for pid in $the_pids; do - verbose_print adb shell kill "$@" "$pid" - adb shell kill "$@" "$pid" - done -} - -get_activity_name() { - local package="$1" - local action_key="android.intent.action.MAIN:" - - # Example query-activities output being parsed: - # - # Activity #14: - # priority=0 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=true - # com.google.android.videos/com.google.android.youtube.videos.EntryPoint - # Activity #15: - # priority=0 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=true - # com.google.android.youtube/.app.honeycomb.Shell$HomeActivity - - # Given package 'com.google.android.youtube' return '.app.honeycomb.Shell$HomeActivity' - - local activity_line="$(adb shell cmd package query-activities --brief -a android.intent.action.MAIN -c android.intent.category.LAUNCHER | grep "$package/")" - IFS="/" read -a array <<< "$activity_line" - local activity_name="${array[1]}" - - # Activities starting with '.' are shorthand for having their package name prefixed. - if [[ $activity_name == .* ]]; then - activity_name="${package}${activity_name}" - fi - echo "$activity_name" -} - -# Use with logcat_from_timestamp to skip all past log-lines. -logcat_save_timestamp() { - adb shell 'date -u +"%Y-%m-%d %H:%M:%S.%N"' -} - -# Roll forward logcat to only show events -# since the specified timestamp. -# -# i.e. don't look at historical logcat, -# only look at FUTURE logcat. -# -# First use 'logcat_save_timestamp' -# Then do whatever action you want. -# Then use 'logcat_from_timestamp_bg $timestamp' -logcat_from_timestamp_bg() { - local timestamp="$1" - shift # drop timestamp from args. - verbose_print adb logcat -T \"$timestamp\" \"$@\" - adb logcat -v UTC -T "$timestamp" "$@" & - logcat_from_timestamp_pid=$! -} - -# Starting at timestamp $2, wait until we seen pattern $3 -# or until a timeout happens in $1 seconds. -# If successful, also echo the line that matched the pattern. -# -# Set VERBOSE_LOGCAT=1 to debug every line of logcat it tries to parse. -logcat_select_pattern() { - local timeout="$1" - local timestamp="$2" - local pattern="$3" - - local logcat_fd - - coproc logcat_fd { - kill_children_quietly() { - kill "$logcat_pidd" - wait "$logcat_pidd" 2>/dev/null - } - - trap 'kill_children_quietly' EXIT # kill logcat when this coproc is killed. - - # run logcat in the background so it can be killed. - logcat_from_timestamp_bg "$timestamp" - logcat_pidd=$logcat_from_timestamp_pid - wait "$logcat_pidd" - } - local logcat_pid="$!" - verbose_print "[LOGCAT] Spawn pid $logcat_pid" - - local timeout_ts="$(date -d "now + ${timeout} seconds" '+%s')" - local now_ts="0" - - local return_code=1 - - verbose_print "logcat_wait_for_pattern begin" - - while read -t "$timeout" -r -u "${logcat_fd[0]}" logcat_output; do - if (( $VERBOSE_LOGCAT )); then - verbose_print "LOGCAT: $logcat_output" - fi - if [[ "$logcat_output:" == *"$pattern"* ]]; then - verbose_print "LOGCAT: " "$logcat_output" - verbose_print "WE DID SEE PATTERN" '<<' "$pattern" '>>.' - echo "$logcat_output" - return_code=0 - break - fi - now_ts="$(date -d "now" '+%s')" - if (( now_ts >= timeout_ts )); then - verbose_print "DID TIMEOUT BEFORE SEEING ANYTHING (timeout=$timeout seconds) " '<<' "$pattern" '>>.' - break - fi - done - - # Don't leave logcat lying around since it will keep going. - kill "$logcat_pid" - # Suppress annoying 'Terminated...' message. - wait "$logcat_pid" 2>/dev/null - - verbose_print "[LOGCAT] $logcat_pid should be killed" - - return $return_code -} - -# Starting at timestamp $2, wait until we seen pattern $3 -# or until a timeout happens in $1 seconds. -# -# Set VERBOSE_LOGCAT=1 to debug every line of logcat it tries to parse. -logcat_wait_for_pattern() { - logcat_select_pattern "$@" > /dev/null -} - -# Starting at timestamp $2, wait until we seen pattern $3 -# or until a timeout happens in $1 seconds. -# If successful, extract with the regular expression pattern in #4 -# and return the first capture group. -# -# Set VERBOSE_LOGCAT=1 to debug every line of logcat it tries to parse. -logcat_extract_pattern() { - local timeout="$1" - local timestamp="$2" - local pattern="$3" - local re_pattern="$4" - - local result - local exit_code - - result="$(logcat_select_pattern "$@")" - exit_code=$? - - if [[ $exit_code -ne 0 ]]; then - return $exit_code - fi - - echo "$result" | sed 's/'"$re_pattern"'/\1/g' -} - -# Join array -# FOO=(a b c) -# join_by , "${FOO[@]}" #a,b,c -join_by() { - local IFS="$1" - shift - echo "$*" -} diff --git a/startop/scripts/app_startup/lib/data_frame.py b/startop/scripts/app_startup/lib/data_frame.py deleted file mode 100644 index 20a2308637f2..000000000000 --- a/startop/scripts/app_startup/lib/data_frame.py +++ /dev/null @@ -1,201 +0,0 @@ -import itertools -from typing import Dict, List - -class DataFrame: - """Table-like class for storing a 2D cells table with named columns.""" - def __init__(self, data: Dict[str, List[object]] = {}): - """ - Create a new DataFrame from a dictionary (keys = headers, - values = columns). - """ - self._headers = [i for i in data.keys()] - self._rows = [] - - row_num = 0 - - def get_data_row(idx): - r = {} - for header, header_data in data.items(): - - if not len(header_data) > idx: - continue - - r[header] = header_data[idx] - - return r - - while True: - row_dict = get_data_row(row_num) - if len(row_dict) == 0: - break - - self._append_row(row_dict.keys(), row_dict.values()) - row_num = row_num + 1 - - def concat_rows(self, other: 'DataFrame') -> None: - """ - In-place concatenate rows of other into the rows of the - current DataFrame. - - None is added in pre-existing cells if new headers - are introduced. - """ - other_datas = other._data_only() - - other_headers = other.headers - - for d in other_datas: - self._append_row(other_headers, d) - - def _append_row(self, headers: List[str], data: List[object]): - new_row = {k:v for k,v in zip(headers, data)} - self._rows.append(new_row) - - for header in headers: - if not header in self._headers: - self._headers.append(header) - - def __repr__(self): -# return repr(self._rows) - repr = "" - - header_list = self._headers_only() - - row_format = u"" - for header in header_list: - row_format = row_format + u"{:>%d}" %(len(header) + 1) - - repr = row_format.format(*header_list) + "\n" - - for v in self._data_only(): - repr = repr + row_format.format(*v) + "\n" - - return repr - - def __eq__(self, other): - if isinstance(other, self.__class__): - return self.headers == other.headers and self.data_table == other.data_table - else: - print("wrong instance", other.__class__) - return False - - @property - def headers(self) -> List[str]: - return [i for i in self._headers_only()] - - @property - def data_table(self) -> List[List[object]]: - return list(self._data_only()) - - @property - def data_table_transposed(self) -> List[List[object]]: - return list(self._transposed_data()) - - @property - def data_row_len(self) -> int: - return len(self._rows) - - def data_row_at(self, idx) -> List[object]: - """ - Return a single data row at the specified index (0th based). - - Accepts negative indices, e.g. -1 is last row. - """ - row_dict = self._rows[idx] - l = [] - - for h in self._headers_only(): - l.append(row_dict.get(h)) # Adds None in blank spots. - - return l - - def copy(self) -> 'DataFrame': - """ - Shallow copy of this DataFrame. - """ - return self.repeat(count=0) - - def repeat(self, count: int) -> 'DataFrame': - """ - Returns a new DataFrame where each row of this dataframe is repeated count times. - A repeat of a row is adjacent to other repeats of that same row. - """ - df = DataFrame() - df._headers = self._headers.copy() - - rows = [] - for row in self._rows: - for i in range(count): - rows.append(row.copy()) - - df._rows = rows - - return df - - def merge_data_columns(self, other: 'DataFrame'): - """ - Merge self and another DataFrame by adding the data from other column-wise. - For any headers that are the same, data from 'other' is preferred. - """ - for h in other._headers: - if not h in self._headers: - self._headers.append(h) - - append_rows = [] - - for self_dict, other_dict in itertools.zip_longest(self._rows, other._rows): - if not self_dict: - d = {} - append_rows.append(d) - else: - d = self_dict - - d_other = other_dict - if d_other: - for k,v in d_other.items(): - d[k] = v - - for r in append_rows: - self._rows.append(r) - - def data_row_reduce(self, fnc) -> 'DataFrame': - """ - Reduces the data row-wise by applying the fnc to each row (column-wise). - Empty cells are skipped. - - fnc(Iterable[object]) -> object - fnc is applied over every non-empty cell in that column (descending row-wise). - - Example: - DataFrame({'a':[1,2,3]}).data_row_reduce(sum) == DataFrame({'a':[6]}) - - Returns a new single-row DataFrame. - """ - df = DataFrame() - df._headers = self._headers.copy() - - def yield_by_column(header_key): - for row_dict in self._rows: - val = row_dict.get(header_key) - if val: - yield val - - new_row_dict = {} - for h in df._headers: - cell_value = fnc(yield_by_column(h)) - new_row_dict[h] = cell_value - - df._rows = [new_row_dict] - return df - - def _headers_only(self): - return self._headers - - def _data_only(self): - row_len = len(self._rows) - - for i in range(row_len): - yield self.data_row_at(i) - - def _transposed_data(self): - return zip(*self._data_only())
\ No newline at end of file diff --git a/startop/scripts/app_startup/lib/data_frame_test.py b/startop/scripts/app_startup/lib/data_frame_test.py deleted file mode 100644 index 1cbc1cbe45cb..000000000000 --- a/startop/scripts/app_startup/lib/data_frame_test.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -"""Unit tests for the data_frame.py script.""" - -from data_frame import DataFrame - -def test_data_frame(): - # trivial empty data frame - df = DataFrame() - assert df.headers == [] - assert df.data_table == [] - assert df.data_table_transposed == [] - - # common case, same number of values in each place. - df = DataFrame({'TotalTime_ms': [1, 2, 3], 'Displayed_ms': [4, 5, 6]}) - assert df.headers == ['TotalTime_ms', 'Displayed_ms'] - assert df.data_table == [[1, 4], [2, 5], [3, 6]] - assert df.data_table_transposed == [(1, 2, 3), (4, 5, 6)] - - # varying num values. - df = DataFrame({'many': [1, 2], 'none': []}) - assert df.headers == ['many', 'none'] - assert df.data_table == [[1, None], [2, None]] - assert df.data_table_transposed == [(1, 2), (None, None)] - - df = DataFrame({'many': [], 'none': [1, 2]}) - assert df.headers == ['many', 'none'] - assert df.data_table == [[None, 1], [None, 2]] - assert df.data_table_transposed == [(None, None), (1, 2)] - - # merge multiple data frames - df = DataFrame() - df.concat_rows(DataFrame()) - assert df.headers == [] - assert df.data_table == [] - assert df.data_table_transposed == [] - - df = DataFrame() - df2 = DataFrame({'TotalTime_ms': [1, 2, 3], 'Displayed_ms': [4, 5, 6]}) - - df.concat_rows(df2) - assert df.headers == ['TotalTime_ms', 'Displayed_ms'] - assert df.data_table == [[1, 4], [2, 5], [3, 6]] - assert df.data_table_transposed == [(1, 2, 3), (4, 5, 6)] - - df = DataFrame({'TotalTime_ms': [1, 2]}) - df2 = DataFrame({'Displayed_ms': [4, 5]}) - - df.concat_rows(df2) - assert df.headers == ['TotalTime_ms', 'Displayed_ms'] - assert df.data_table == [[1, None], [2, None], [None, 4], [None, 5]] - - df = DataFrame({'TotalTime_ms': [1, 2]}) - df2 = DataFrame({'TotalTime_ms': [3, 4], 'Displayed_ms': [5, 6]}) - - df.concat_rows(df2) - assert df.headers == ['TotalTime_ms', 'Displayed_ms'] - assert df.data_table == [[1, None], [2, None], [3, 5], [4, 6]] - - # data_row_at - df = DataFrame({'TotalTime_ms': [1, 2, 3], 'Displayed_ms': [4, 5, 6]}) - assert df.data_row_at(-1) == [3, 6] - assert df.data_row_at(2) == [3, 6] - assert df.data_row_at(1) == [2, 5] - - # repeat - df = DataFrame({'TotalTime_ms': [1], 'Displayed_ms': [4]}) - df2 = DataFrame({'TotalTime_ms': [1, 1, 1], 'Displayed_ms': [4, 4, 4]}) - assert df.repeat(3) == df2 - - # repeat - df = DataFrame({'TotalTime_ms': [1, 1, 1], 'Displayed_ms': [4, 4, 4]}) - assert df.data_row_len == 3 - df = DataFrame({'TotalTime_ms': [1, 1]}) - assert df.data_row_len == 2 - - # repeat - df = DataFrame({'TotalTime_ms': [1, 1, 1], 'Displayed_ms': [4, 4, 4]}) - assert df.data_row_len == 3 - df = DataFrame({'TotalTime_ms': [1, 1]}) - assert df.data_row_len == 2 - - # data_row_reduce - df = DataFrame({'TotalTime_ms': [1, 1, 1], 'Displayed_ms': [4, 4, 4]}) - df_sum = DataFrame({'TotalTime_ms': [3], 'Displayed_ms': [12]}) - assert df.data_row_reduce(sum) == df_sum - - # merge_data_columns - df = DataFrame({'TotalTime_ms': [1, 2, 3]}) - df2 = DataFrame({'Displayed_ms': [3, 4, 5, 6]}) - - df.merge_data_columns(df2) - assert df == DataFrame( - {'TotalTime_ms': [1, 2, 3], 'Displayed_ms': [3, 4, 5, 6]}) - - df = DataFrame({'TotalTime_ms': [1, 2, 3]}) - df2 = DataFrame({'Displayed_ms': [3, 4]}) - - df.merge_data_columns(df2) - assert df == DataFrame( - {'TotalTime_ms': [1, 2, 3], 'Displayed_ms': [3, 4]}) - - df = DataFrame({'TotalTime_ms': [1, 2, 3]}) - df2 = DataFrame({'TotalTime_ms': [10, 11]}) - - df.merge_data_columns(df2) - assert df == DataFrame({'TotalTime_ms': [10, 11, 3]}) - - df = DataFrame({'TotalTime_ms': []}) - df2 = DataFrame({'TotalTime_ms': [10, 11]}) - - df.merge_data_columns(df2) - assert df == DataFrame({'TotalTime_ms': [10, 11]}) diff --git a/startop/scripts/app_startup/lib/perfetto_trace_collector.py b/startop/scripts/app_startup/lib/perfetto_trace_collector.py deleted file mode 100644 index 9ffb3494da49..000000000000 --- a/startop/scripts/app_startup/lib/perfetto_trace_collector.py +++ /dev/null @@ -1,166 +0,0 @@ -# Copyright 2019, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Class to collector perfetto trace.""" -import datetime -import os -import re -import sys -import time -from datetime import timedelta -from typing import Optional, List, Tuple - -# global variables -DIR = os.path.abspath(os.path.dirname(__file__)) - -sys.path.append(os.path.dirname(os.path.dirname(DIR))) - -import app_startup.lib.adb_utils as adb_utils -from app_startup.lib.app_runner import AppRunner, AppRunnerListener -import lib.print_utils as print_utils -import lib.logcat_utils as logcat_utils -import iorap.lib.iorapd_utils as iorapd_utils - -class PerfettoTraceCollector(AppRunnerListener): - """ Class to collect perfetto trace. - - To set trace duration of perfetto, change the 'trace_duration_ms'. - To pull the generated perfetto trace on device, set the 'output'. - """ - TRACE_FILE_SUFFIX = 'perfetto_trace.pb' - TRACE_DURATION_PROP = 'iorapd.perfetto.trace_duration_ms' - MS_PER_SEC = 1000 - DEFAULT_TRACE_DURATION = timedelta(milliseconds=5000) # 5 seconds - _COLLECTOR_TIMEOUT_MULTIPLIER = 10 # take the regular timeout and multiply - - def __init__(self, - package: str, - activity: Optional[str], - compiler_filter: Optional[str], - timeout: Optional[int], - simulate: bool, - trace_duration: timedelta = DEFAULT_TRACE_DURATION, - save_destination_file_path: Optional[str] = None): - """ Initialize the perfetto trace collector. """ - self.app_runner = AppRunner(package, - activity, - compiler_filter, - timeout, - simulate) - self.app_runner.add_callbacks(self) - - self.trace_duration = trace_duration - self.save_destination_file_path = save_destination_file_path - - def purge_file(self, suffix: str) -> None: - print_utils.debug_print('iorapd-perfetto: purge file in ' + - self._get_remote_path()) - adb_utils.delete_file_on_device(self._get_remote_path()) - - def run(self) -> Optional[List[Tuple[str]]]: - """Runs an app. - - Returns: - A list of (metric, value) tuples. - """ - return self.app_runner.run() - - def preprocess(self): - # Sets up adb environment. - adb_utils.root() - adb_utils.disable_selinux() - time.sleep(1) - - # Kill any existing process of this app - adb_utils.pkill(self.app_runner.package) - - # Remove existing trace and compiler files - self.purge_file(PerfettoTraceCollector.TRACE_FILE_SUFFIX) - - # Set perfetto trace duration prop to milliseconds. - adb_utils.set_prop(PerfettoTraceCollector.TRACE_DURATION_PROP, - int(self.trace_duration.total_seconds()* - PerfettoTraceCollector.MS_PER_SEC)) - - if not iorapd_utils.stop_iorapd(): - raise RuntimeError('Cannot stop iorapd!') - - if not iorapd_utils.enable_iorapd_perfetto(): - raise RuntimeError('Cannot enable perfetto!') - - if not iorapd_utils.disable_iorapd_readahead(): - raise RuntimeError('Cannot disable readahead!') - - if not iorapd_utils.start_iorapd(): - raise RuntimeError('Cannot start iorapd!') - - # Drop all caches to get cold starts. - adb_utils.vm_drop_cache() - - def postprocess(self, pre_launch_timestamp: str): - # Kill any existing process of this app - adb_utils.pkill(self.app_runner.package) - - iorapd_utils.disable_iorapd_perfetto() - - if self.save_destination_file_path is not None: - adb_utils.pull_file(self._get_remote_path(), - self.save_destination_file_path) - - def metrics_selector(self, am_start_output: str, - pre_launch_timestamp: str) -> str: - """Parses the metric after app startup by reading from logcat in a blocking - manner until all metrics have been found". - - Returns: - An empty string because the metric needs no further parsing. - """ - if not self._wait_for_perfetto_trace(pre_launch_timestamp): - raise RuntimeError('Could not save perfetto app trace file!') - - return '' - - def _wait_for_perfetto_trace(self, pre_launch_timestamp) -> Optional[str]: - """ Waits for the perfetto trace being saved to file. - - The string is in the format of r".*Perfetto TraceBuffer saved to file: - <file path>.*" - - Returns: - the string what the program waits for. If the string doesn't show up, - return None. - """ - pattern = re.compile(r'.*Perfetto TraceBuffer saved to file: {}.*'. - format(self._get_remote_path())) - - # The pre_launch_timestamp is longer than what the datetime can parse. Trim - # last three digits to make them align. For example: - # 2019-07-02 23:20:06.972674825999 -> 2019-07-02 23:20:06.972674825 - assert len(pre_launch_timestamp) == len('2019-07-02 23:20:06.972674825') - timestamp = datetime.datetime.strptime(pre_launch_timestamp[:-3], - '%Y-%m-%d %H:%M:%S.%f') - - # The timeout of perfetto trace is longer than the normal app run timeout. - timeout_dt = self.app_runner.timeout * PerfettoTraceCollector._COLLECTOR_TIMEOUT_MULTIPLIER - timeout_end = timestamp + datetime.timedelta(seconds=timeout_dt) - - return logcat_utils.blocking_wait_for_logcat_pattern(timestamp, - pattern, - timeout_end) - - def _get_remote_path(self): - # For example: android.music%2Fmusic.TopLevelActivity.perfetto_trace.pb - return iorapd_utils._iorapd_path_to_data_file(self.app_runner.package, - self.app_runner.activity, - PerfettoTraceCollector.TRACE_FILE_SUFFIX) diff --git a/startop/scripts/app_startup/lib/perfetto_trace_collector_test.py b/startop/scripts/app_startup/lib/perfetto_trace_collector_test.py deleted file mode 100644 index 8d94fc58bede..000000000000 --- a/startop/scripts/app_startup/lib/perfetto_trace_collector_test.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. -# - -"""Unit tests for the data_frame.py script.""" -import os -import sys -from pathlib import Path -from datetime import timedelta - -from mock import call, patch -from perfetto_trace_collector import PerfettoTraceCollector - -sys.path.append(Path(os.path.realpath(__file__)).parents[2]) -from app_startup.lib.app_runner import AppRunner - -RUNNER = PerfettoTraceCollector(package='music', - activity='MainActivity', - compiler_filter=None, - timeout=10, - simulate=False, - trace_duration = timedelta(milliseconds=1000), - # No actual file will be created. Just to - # check the command. - save_destination_file_path='/tmp/trace.pb') - -def _mocked_run_shell_command(*args, **kwargs): - if args[0] == 'adb shell ps | grep "music" | awk \'{print $2;}\'': - return (True, '9999') - else: - return (True, '') - -@patch('lib.logcat_utils.blocking_wait_for_logcat_pattern') -@patch('lib.cmd_utils.run_shell_command') -def test_perfetto_trace_collector_preprocess(mock_run_shell_command, - mock_blocking_wait_for_logcat_pattern): - mock_run_shell_command.side_effect = _mocked_run_shell_command - mock_blocking_wait_for_logcat_pattern.return_value = "Succeed!" - - RUNNER.preprocess() - - calls = [call('adb root'), - call('adb shell "getenforce"'), - call('adb shell "setenforce 0"'), - call('adb shell "stop"'), - call('adb shell "start"'), - call('adb wait-for-device'), - call('adb shell ps | grep "music" | awk \'{print $2;}\''), - call('adb shell "kill 9999"'), - call( - 'adb shell "[[ -f \'/data/misc/iorapd/music%2FMainActivity.perfetto_trace.pb\' ]] ' - '&& rm -f \'/data/misc/iorapd/music%2FMainActivity.perfetto_trace.pb\' || exit 0"'), - call('adb shell "setprop "iorapd.perfetto.trace_duration_ms" "1000""'), - call( - 'bash -c "source {}; iorapd_stop"'.format( - AppRunner.IORAP_COMMON_BASH_SCRIPT)), - call( - 'bash -c "source {}; iorapd_perfetto_enable"'.format( - AppRunner.IORAP_COMMON_BASH_SCRIPT)), - call( - 'bash -c "source {}; iorapd_readahead_disable"'.format( - AppRunner.IORAP_COMMON_BASH_SCRIPT)), - call( - 'bash -c "source {}; iorapd_start"'.format( - AppRunner.IORAP_COMMON_BASH_SCRIPT)), - call('adb shell "echo 3 > /proc/sys/vm/drop_caches"')] - - mock_run_shell_command.assert_has_calls(calls) - -@patch('lib.logcat_utils.blocking_wait_for_logcat_pattern') -@patch('lib.cmd_utils.run_shell_command') -def test_perfetto_trace_collector_postprocess(mock_run_shell_command, - mock_blocking_wait_for_logcat_pattern): - mock_run_shell_command.side_effect = _mocked_run_shell_command - mock_blocking_wait_for_logcat_pattern.return_value = "Succeed!" - - RUNNER.postprocess('2019-07-02 23:20:06.972674825') - - calls = [call('adb shell ps | grep "music" | awk \'{print $2;}\''), - call('adb shell "kill 9999"'), - call( - 'bash -c "source {}; iorapd_perfetto_disable"'.format( - AppRunner.IORAP_COMMON_BASH_SCRIPT)), - call('adb pull ' - '"/data/misc/iorapd/music%2FMainActivity.perfetto_trace.pb" ' - '"/tmp/trace.pb"')] - - mock_run_shell_command.assert_has_calls(calls) diff --git a/startop/scripts/app_startup/parse_metrics b/startop/scripts/app_startup/parse_metrics deleted file mode 100755 index 3fa1462bc56e..000000000000 --- a/startop/scripts/app_startup/parse_metrics +++ /dev/null @@ -1,215 +0,0 @@ -#!/bin/bash -# -# 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. - -usage() { - cat <<EOF -Usage: launch_application package activity | parse_metrics --package <name> --timestamp <timestamp> [OPTIONS]... - - Reads from stdin the result of 'am start' metrics. May also parse logcat - for additional metrics. - - Output form: - - MetricName_unit=numeric_value - MetricName2_unit=numeric_value2 - - This may block until all desired metrics are parsed from logcat. - To get a list of metrics without doing real parsing, use --simulate. - - To add package-specific metrics, add a script called 'metrics/\$full_package_name' - that exposes additional metrics in same way as above. - - (required) - -p, --package <name> package of the app that is being used - -ts, --timestamp <name> logcat timestamp [only looks at logcat entries after this timestamp]. - - (optional) - -s, --simulate prints dummy values instead of real metrics - -a, --activity <name> activity to use (default: inferred) - -h, --help usage information (this) - -v, --verbose enable extra verbose printing - -t, --timeout <sec> how many seconds to timeout when trying to wait for logcat to change - -rfd, --reportfullydrawn wait for report fully drawn (default: off) -EOF -} - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -source "$DIR/lib/common" - -report_fully_drawn="n" -package="" -activity="" -timeout=5 -simulate="n" -parse_arguments() { - while [[ $# -gt 0 ]]; do - case "$1" in - -h|--help) - usage - exit 0 - ;; - -p|--package) - package="$2" - shift - ;; - -a|--activity) - activity="$2" - shift - ;; - -v|--verbose) - export verbose="y" - ;; - -t|--timeout) - timeout="$2" - shift - ;; - -ts|--timestamp) - timestamp="$2" - shift - ;; - -s|--simulate) - simulate="y" - ;; - -rfd|--reportfullydrawn) - report_fully_drawn="y" - ;; - - - *) - echo "Invalid argument: $1" >&2 - exit 1 - esac - shift - done -} - -# Main entry point -if [[ $# -eq 0 ]]; then - usage - exit 1 -else - parse_arguments "$@" - - # if we do not have have package exit early with an error - [[ "$package" == "" ]] && echo "--package not specified" 1>&2 && exit 64 - - # ignore timestamp for --simulate. it's optional. - if [[ $simulate == y ]]; then - timestamp=0 - fi - - # if we do not have timestamp, exit early with an error - [[ "$timestamp" == "" ]] && echo "--timestamp not specified" 1>&2 && exit 64 - - if [[ "$activity" == "" ]] && [[ "$simulate" != "y" ]]; then - activity="$(get_activity_name "$package")" - if [[ "$activity" == "" ]]; then - echo "Activity name could not be found, invalid package name?" 1>&2 - exit 64 - else - verbose_print "Activity name inferred: " "$activity" - fi - fi -fi - -parse_metric_from_logcat() { - local metric_name="$1" - local pattern="$2" - local re_pattern="$3" - local retcode - local result - local sec - local ms - - # parse logcat for 'Displayed...' and that other one... - - # 05-06 14:34:08.854 29460 29481 I ActivityTaskManager: Displayed com.google.android.dialer/.extensions.GoogleDialtactsActivity: +361ms - verbose_print "parse_metric_from_logcat: $re_pattern" - - - echo -ne "$metric_name=" - - if [[ $simulate == y ]]; then - echo "-1" - return 0 - fi - - result="$(logcat_extract_pattern "$timeout" "$timestamp" "$pattern" "$re_pattern")" - retcode=$? - - if [[ $retcode -ne 0 ]]; then - # Timed out before finding the pattern. Could also mean the pattern is wrong. - echo "Parse $re_pattern from logcat TIMED OUT after $timeout seconds." >&2 - echo "-$?" - return $retcode - fi - - # "10s123ms" -> "10s123" - result=${result/ms/} - if [[ $result =~ s ]]; then - ms=${result/*s/} - sec=${result/s*/} - else - sec=0 - ms=$result - fi - ((result=sec*1000+ms)) - - echo "$result" - return $retcode -} - - -total_time="-1" -if [[ $simulate != y ]]; then - verbose_print 'logcat timestamp NOW: ' $(logcat_save_timestamp) - - # parse stdin for 'am start' result - while read -t "$timeout" -r input_line; do - verbose_print 'stdin:' "$input_line" - if [[ $input_line == *TotalTime:* ]]; then - total_time="$(echo "$input_line" | sed 's/TotalTime: \([[:digit:]]\+\)/\1/g')" - # but keep reading the rest from stdin until <EOF> - fi - done -fi - -echo "TotalTime_ms=$total_time" - -# parse logcat for 'Displayed...' and that other one... - -# 05-06 14:34:08.854 29460 29481 I ActivityTaskManager: Displayed com.google.android.dialer/.extensions.GoogleDialtactsActivity: +361ms -pattern="ActivityTaskManager: Displayed ${package}" -re_pattern='.*Displayed[[:blank:]]\+'"${package}"'[/][^[:blank:]]\+[[:blank:]]+\([[:digit:]]\+ms\|[[:digit:]]\+s[[:digit:]]\+ms\).*' - -parse_metric_from_logcat "Displayed_ms" "$pattern" "$re_pattern" - -# Only track ReportFullyDrawn with --reportfullydrawn/-rfd flags -if [[ $report_fully_drawn == y ]]; then - # 01-16 17:31:44.550 11172 11204 I ActivityTaskManager: Fully drawn com.google.android.GoogleCamera/com.android.camera.CameraLauncher: +10s897ms - pattern="ActivityTaskManager: Fully drawn ${package}" - #re_pattern='.*Fully drawn[[:blank:]]\+'"${package}"'[/][^[:blank:]]\+[[:blank:]]+\([[:digit:]]\+\).*' - re_pattern='.*Fully drawn[[:blank:]]\+'"${package}"'[/][^[:blank:]]\+[[:blank:]]+\([[:digit:]]\+ms\|[[:digit:]]\+s[[:digit:]]\+ms\).*' - - parse_metric_from_logcat "Fully_drawn_ms" "$pattern" "$re_pattern" -fi - -# also call into package-specific scripts if there are additional metrics -if [[ -x "$DIR/metrics/$package" ]]; then - source "$DIR/metrics/$package" "$timestamp" -else - verbose_print parse_metrics: no per-package metrics script found at "$DIR/metrics/$package" -fi diff --git a/startop/scripts/app_startup/query_compiler_filter.py b/startop/scripts/app_startup/query_compiler_filter.py deleted file mode 100755 index ea14264b4a1c..000000000000 --- a/startop/scripts/app_startup/query_compiler_filter.py +++ /dev/null @@ -1,232 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# -# -# Query the current compiler filter for an application by its package name. -# (By parsing the results of the 'adb shell dumpsys package $package' command). -# The output is a string "$compilation_filter $compilation_reason $isa". -# -# See --help for more details. -# -# ----------------------------------- -# -# Sample usage: -# -# $> ./query_compiler_filter.py --package com.google.android.calculator -# speed-profile unknown arm64 -# - -import argparse -import os -import re -import sys - -# TODO: refactor this with a common library file with analyze_metrics.py -DIR = os.path.abspath(os.path.dirname(__file__)) -sys.path.append(os.path.dirname(DIR)) -import lib.cmd_utils as cmd_utils -import lib.print_utils as print_utils - -from typing import List, NamedTuple, Iterable - -_DEBUG_FORCE = None # Ignore -d/--debug if this is not none. - -def parse_options(argv: List[str] = None): - """Parse command line arguments and return an argparse Namespace object.""" - parser = argparse.ArgumentParser(description="Query the compiler filter for a package.") - # argparse considers args starting with - and -- optional in --help, even though required=True. - # by using a named argument group --help will clearly say that it's required instead of optional. - required_named = parser.add_argument_group('required named arguments') - required_named.add_argument('-p', '--package', action='store', dest='package', help='package of the application', required=True) - - # optional arguments - # use a group here to get the required arguments to appear 'above' the optional arguments in help. - optional_named = parser.add_argument_group('optional named arguments') - optional_named.add_argument('-i', '--isa', '--instruction-set', action='store', dest='instruction_set', help='which instruction set to select. defaults to the first one available if not specified.', choices=('arm64', 'arm', 'x86_64', 'x86')) - optional_named.add_argument('-s', '--simulate', dest='simulate', action='store_true', help='Print which commands will run, but don\'t run the apps') - optional_named.add_argument('-d', '--debug', dest='debug', action='store_true', help='Add extra debugging output') - - return parser.parse_args(argv) - -def remote_dumpsys_package(package: str, simulate: bool) -> str: - # --simulate is used for interactive debugging/development, but also for the unit test. - if simulate: - return """ -Dexopt state: - [%s] - path: /data/app/%s-D7s8PLidqqEq7Jc7UH_a5A==/base.apk - arm64: [status=speed-profile] [reason=unknown] - path: /data/app/%s-D7s8PLidqqEq7Jc7UH_a5A==/base.apk - arm: [status=speed] [reason=first-boot] - path: /data/app/%s-D7s8PLidqqEq7Jc7UH_a5A==/base.apk - x86: [status=quicken] [reason=install] -""" %(package, package, package, package) - - code, res = cmd_utils.execute_arbitrary_command(['adb', 'shell', 'dumpsys', - 'package', package], - simulate=False, - timeout=5, - shell=False) - if code: - return res - else: - raise AssertionError("Failed to dumpsys package, errors = %s", res) - -ParseTree = NamedTuple('ParseTree', [('label', str), ('children', List['ParseTree'])]) -DexoptState = ParseTree # With the Dexopt state: label -ParseResult = NamedTuple('ParseResult', [('remainder', List[str]), ('tree', ParseTree)]) - -def find_parse_subtree(parse_tree: ParseTree, match_regex: str) -> ParseTree: - if re.match(match_regex, parse_tree.label): - return parse_tree - - for node in parse_tree.children: - res = find_parse_subtree(node, match_regex) - if res: - return res - - return None - -def find_parse_children(parse_tree: ParseTree, match_regex: str) -> Iterable[ParseTree]: - for node in parse_tree.children: - if re.match(match_regex, node.label): - yield node - -def parse_tab_subtree(label: str, str_lines: List[str], separator=' ', indent=-1) -> ParseResult: - children = [] - - get_indent_level = lambda line: len(line) - len(line.lstrip()) - - line_num = 0 - - keep_going = True - while keep_going: - keep_going = False - - for line_num in range(len(str_lines)): - line = str_lines[line_num] - current_indent = get_indent_level(line) - - print_utils.debug_print("INDENT=%d, LINE=%s" %(current_indent, line)) - - current_label = line.lstrip() - - # skip empty lines - if line.lstrip() == "": - continue - - if current_indent > indent: - parse_result = parse_tab_subtree(current_label, str_lines[line_num+1::], separator, current_indent) - str_lines = parse_result.remainder - children.append(parse_result.tree) - keep_going = True - else: - # current_indent <= indent - keep_going = False - - break - - new_remainder = str_lines[line_num::] - print_utils.debug_print("NEW REMAINDER: ", new_remainder) - - parse_tree = ParseTree(label, children) - return ParseResult(new_remainder, parse_tree) - -def parse_tab_tree(str_tree: str, separator=' ', indentation_level=-1) -> ParseTree: - - label = None - lst = [] - - line_num = 0 - line_lst = str_tree.split("\n") - - return parse_tab_subtree("", line_lst, separator, indentation_level).tree - -def parse_dexopt_state(dumpsys_tree: ParseTree) -> DexoptState: - res = find_parse_subtree(dumpsys_tree, "Dexopt(\s+)state[:]?") - if not res: - raise AssertionError("Could not find the Dexopt state") - return res - -def find_first_compiler_filter(dexopt_state: DexoptState, package: str, instruction_set: str) -> str: - lst = find_all_compiler_filters(dexopt_state, package) - - print_utils.debug_print("all compiler filters: ", lst) - - for compiler_filter_info in lst: - if not instruction_set: - return compiler_filter_info - - if compiler_filter_info.isa == instruction_set: - return compiler_filter_info - - return None - -CompilerFilterInfo = NamedTuple('CompilerFilterInfo', [('isa', str), ('status', str), ('reason', str)]) - -def find_all_compiler_filters(dexopt_state: DexoptState, package: str) -> List[CompilerFilterInfo]: - - lst = [] - package_tree = find_parse_subtree(dexopt_state, re.escape("[%s]" %package)) - - if not package_tree: - raise AssertionError("Could not find any package subtree for package %s" %(package)) - - print_utils.debug_print("package tree: ", package_tree) - - for path_tree in find_parse_children(package_tree, "path: "): - print_utils.debug_print("path tree: ", path_tree) - - matchre = re.compile("([^:]+):\s+\[status=([^\]]+)\]\s+\[reason=([^\]]+)\]") - - for isa_node in find_parse_children(path_tree, matchre): - - matches = re.match(matchre, isa_node.label).groups() - - info = CompilerFilterInfo(*matches) - lst.append(info) - - return lst - -def main() -> int: - opts = parse_options() - cmd_utils._debug = opts.debug - if _DEBUG_FORCE is not None: - cmd_utils._debug = _DEBUG_FORCE - print_utils.debug_print("parsed options: ", opts) - - # Note: This can often 'fail' if the package isn't actually installed. - package_dumpsys = remote_dumpsys_package(opts.package, opts.simulate) - print_utils.debug_print("package dumpsys: ", package_dumpsys) - dumpsys_parse_tree = parse_tab_tree(package_dumpsys, package_dumpsys) - print_utils.debug_print("parse tree: ", dumpsys_parse_tree) - dexopt_state = parse_dexopt_state(dumpsys_parse_tree) - - filter = find_first_compiler_filter(dexopt_state, opts.package, opts.instruction_set) - - if filter: - print(filter.status, end=' ') - print(filter.reason, end=' ') - print(filter.isa) - else: - print("ERROR: Could not find any compiler-filter for package %s, isa %s" %(opts.package, opts.instruction_set), file=sys.stderr) - return 1 - - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/startop/scripts/app_startup/query_compiler_filter_test.py b/startop/scripts/app_startup/query_compiler_filter_test.py deleted file mode 100755 index a751a43d1d6c..000000000000 --- a/startop/scripts/app_startup/query_compiler_filter_test.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -""" -Unit tests for the query_compiler_filter.py script. - -Install: - $> sudo apt-get install python3-pytest ## OR - $> pip install -U pytest -See also https://docs.pytest.org/en/latest/getting-started.html - -Usage: - $> ./query_compiler_filter.py - $> pytest query_compiler_filter.py - $> python -m pytest query_compiler_filter.py - -See also https://docs.pytest.org/en/latest/usage.html -""" - -# global imports -from contextlib import contextmanager -import io -import shlex -import sys -import typing - -# pip imports -import pytest - -# local imports -import query_compiler_filter as qcf - -@contextmanager -def redirect_stdout_stderr(): - """Redirect stdout/stderr to a new StringIO for duration of context.""" - old_stdout = sys.stdout - old_stderr = sys.stderr - new_stdout = io.StringIO() - sys.stdout = new_stdout - new_stderr = io.StringIO() - sys.stderr = new_stderr - try: - yield (new_stdout, new_stderr) - finally: - sys.stdout = old_stdout - sys.stderr = old_stderr - # Seek back to the beginning so we can read whatever was written into it. - new_stdout.seek(0) - new_stderr.seek(0) - -@contextmanager -def replace_argv(argv): - """ Temporarily replace argv for duration of this context.""" - old_argv = sys.argv - sys.argv = [sys.argv[0]] + argv - try: - yield - finally: - sys.argv = old_argv - -def exec_main(argv): - """Run the query_compiler_filter main function with the provided arguments. - - Returns the stdout result when successful, assertion failure otherwise. - """ - try: - with redirect_stdout_stderr() as (the_stdout, the_stderr): - with replace_argv(argv): - code = qcf.main() - assert 0 == code, the_stderr.readlines() - - all_lines = the_stdout.readlines() - return "".join(all_lines) - finally: - the_stdout.close() - the_stderr.close() - -def test_query_compiler_filter(): - # no --instruction-set specified: provide whatever was the 'first' filter. - assert exec_main(['--simulate', - '--package', 'com.google.android.apps.maps']) == \ - "speed-profile unknown arm64\n" - - # specifying an instruction set finds the exact compiler filter match. - assert exec_main(['--simulate', - '--package', 'com.google.android.apps.maps', - '--instruction-set', 'arm64']) == \ - "speed-profile unknown arm64\n" - - assert exec_main(['--simulate', - '--package', 'com.google.android.apps.maps', - '--instruction-set', 'arm']) == \ - "speed first-boot arm\n" - - assert exec_main(['--simulate', - '--debug', - '--package', 'com.google.android.apps.maps', - '--instruction-set', 'x86']) == \ - "quicken install x86\n" - -if __name__ == '__main__': - pytest.main() diff --git a/startop/scripts/app_startup/run_app_with_prefetch b/startop/scripts/app_startup/run_app_with_prefetch deleted file mode 100755 index 31f625334b1e..000000000000 --- a/startop/scripts/app_startup/run_app_with_prefetch +++ /dev/null @@ -1,487 +0,0 @@ -#!/bin/bash -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -usage() { - cat <<EOF -Usage: run_app_with_prefetch --package <name> [OPTIONS]... - - -p, --package <name> package of the app to test - -a, --activity <name> activity to use - -h, --help usage information (this) - -v, --verbose enable extra verbose printing - -i, --input <file> trace file protobuf (default 'TraceFile.pb') - -r, --readahead <mode> cold, warm, fadvise, mlock (default 'warm') - -w, --when <when> aot or jit (default 'jit') - -c, --count <count> how many times to run (default 1) - -s, --sleep <sec> how long to sleep after readahead - -t, --timeout <sec> how many seconds to timeout in between each app run (default 10) - -o, --output <file.csv> what file to write the performance results into as csv (default stdout) -EOF -} - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -source "$DIR/../iorap/common" - -report_fully_drawn="n" -needs_trace_file="n" -input_file="" -package="" -mode='warm' -count=2 -sleep_time=2 -timeout=10 -output="" # stdout by default -when="jit" -parse_arguments() { - while [[ $# -gt 0 ]]; do - case "$1" in - -h|--help) - usage - exit 0 - ;; - -p|--package) - package="$2" - shift - ;; - -a|--activity) - activity="$2" - shift - ;; - -i|--input) - input_file="$2" - shift - ;; - -v|--verbose) - export verbose="y" - ;; - -r|--readahead) - mode="$2" - shift - ;; - -rfd|--reportfullydrawn) - report_fully_drawn="y" - shift - ;; - -c|--count) - count="$2" - ((count+=1)) - shift - ;; - -s|--sleep) - sleep_time="$2" - shift - ;; - -t|--timeout) - timeout="$2" - shift - ;; - -o|--output) - output="$2" - shift - ;; - -w|--when) - when="$2" - shift - ;; - --compiler-filter) - compiler_filter="$2" - shift - ;; - *) - echo "Invalid argument: $1" >&2 - exit 1 - esac - shift - done - - if [[ $when == "aot" ]]; then - # TODO: re-implement aot later for experimenting. - echo "Error: --when $when is unsupported" >&2 - exit 1 - elif [[ $when != "jit" ]]; then - echo "Error: --when must be one of (aot jit)." >&2 - exit 1 - fi -} - -echo_to_output_file() { - if [[ "x$output" != x ]]; then - echo "$@" >> $output - fi - # Always echo to stdout as well. - echo "$@" -} - -find_package_path() { - local pkg="$1" - - res="$(adb shell find "/data/app/$pkg"-'*' -maxdepth 0 2> /dev/null)" - if [[ -z $res ]]; then - res="$(adb shell find "/system/app/$pkg"-'*' -maxdepth 0 2> /dev/null)" - fi - echo "$res" -} - -# Main entry point -if [[ $# -eq 0 ]]; then - usage - exit 1 -else - parse_arguments "$@" - - # if we do not have have package exit early with an error - [[ "$package" == "" ]] && echo "--package not specified" 1>&2 && exit 1 - - if [[ $mode != "cold" && $mode != "warm" ]]; then - needs_trace_file="y" - if [[ -z "$input_file" ]] || ! [[ -f $input_file ]]; then - echo "--input not specified" 1>&2 - exit 1 - fi - fi - - if [[ "$activity" == "" ]]; then - activity="$(get_activity_name "$package")" - if [[ "$activity" == "" ]]; then - echo "Activity name could not be found, invalid package name?" 1>&2 - exit 1 - else - verbose_print "Activity name inferred: " "$activity" - fi - fi -fi - -adb root > /dev/null - -if [[ ($when == jit) || ($when == aot) ]] && [[ "$(adb shell getenforce)" != "Permissive" ]]; then - echo "Disable selinux permissions and restart framework." - adb shell setenforce 0 - adb shell stop - adb shell start - adb wait-for-device -fi - -# TODO: set performance governor etc, preferrably only once -# before every single app run. - -# Kill everything before running. -remote_pkill "$package" -sleep 1 - -timings_array=() - -package_path="$(find_package_path "$package")" -if [[ $? -ne 0 ]]; then - echo "Failed to detect package path for '$package'" >&2 - exit 1 -fi -verbose_print "Package was in path '$package_path'" - -application_trace_file_path="$package_path/TraceFile.pb" -trace_file_directory="$package_path" -if [[ $needs_trace_file == y ]]; then - # system server always passes down the package path in a hardcoded spot. - if [[ $when == "jit" ]]; then - if ! iorapd_compiler_install_trace_file "$package" "$activity" "$input_file"; then - echo "Error: Failed to install compiled TraceFile.pb for '$package/$activity'" >&2 - exit 1 - fi - keep_application_trace_file="y" - else - echo "TODO: --readahead=aot is non-functional and needs to be fixed." >&2 - exit 1 - # otherwise use a temporary directory to get normal non-jit behavior. - trace_file_directory="/data/local/tmp/prefetch/$package" - adb shell mkdir -p "$trace_file_directory" - verbose_print adb push "$input_file" "$trace_file_directory/TraceFile.pb" - adb push "$input_file" "$trace_file_directory/TraceFile.pb" - fi -fi - -# Everything other than JIT: remove the trace file, -# otherwise system server activity hints will kick in -# and the new just-in-time app pre-warmup will happen. -if [[ $keep_application_trace_file == "n" ]]; then - iorapd_compiler_purge_trace_file "$package" "$activity" -fi - -# Perform AOT readahead/pinning/etc when an application is about to be launched. -# For JIT readahead, we allow the system to handle it itself (this is a no-op). -# -# For warm, cold, etc modes which don't need readahead this is always a no-op. -perform_aot() { - local the_when="$1" # user: aot, jit - local the_mode="$2" # warm, cold, fadvise, mlock, etc. - - # iorapd readahead for jit+(mlock/fadvise) - if [[ $the_when == "jit" && $the_mode != 'warm' && $the_mode != 'cold' ]]; then - iorapd_readahead_enable - return 0 - fi - - if [[ $the_when != "aot" ]]; then - # TODO: just in time implementation.. should probably use system server. - return 0 - fi - - # any non-warm/non-cold modes should use the iorap-activity-hint wrapper script. - if [[ $the_mode != 'warm' && $the_mode != 'cold' ]]; then - - # TODO: add activity_hint_sender.exp - verbose_print "starting with package=$package package_path=$trace_file_directory" - coproc hint_sender_fd { $ANDROID_BUILD_TOP/system/iorap/src/sh/activity_hint_sender.exp "$package" "$trace_file_directory" "$the_mode"; } - hint_sender_pid=$! - verbose_print "Activity hint sender began" - - notification_success="n" - while read -r -u "${hint_sender_fd[0]}" hint_sender_output; do - verbose_print "$hint_sender_output" - if [[ "$hint_sender_output" == "Press any key to send completed event..."* ]]; then - verbose_print "WE DID SEE NOTIFICATION SUCCESS." - notification_success='y' - # Give it some time to actually perform the readaheads. - sleep $sleep_time - break - fi - done - - if [[ $notification_success == 'n' ]]; then - echo "[FATAL] Activity hint notification failed." 1>&2 - exit 1 - fi - fi -} - -# Perform cleanup at the end of each loop iteration. -perform_post_launch_cleanup() { - local the_when="$1" # user: aot, jit - local the_mode="$2" # warm, cold, fadvise, mlock, etc. - local logcat_timestamp="$3" # timestamp from before am start. - local res - - if [[ $the_when != "aot" ]]; then - if [[ $the_mode != 'warm' && $the_mode != 'cold' ]]; then - # Validate that readahead completes. - # If this fails for some reason, then this will also discard the timing of the run. - iorapd_readahead_wait_until_finished "$package" "$activity" "$logcat_timestamp" "$timeout" - res=$? - - iorapd_readahead_disable - - return $res - fi - # Don't need to do anything for warm or cold. - return 0 - fi - - # any non-warm/non-cold modes should use the iorap-activity-hint wrapper script. - if [[ $the_mode != 'warm' && $the_mode != 'cold' ]]; then - # Clean up the hint sender by telling it that the launch was completed, - # and to shutdown the watcher. - echo "Done\n" >&"${hint_sender_fd[1]}" - - while read -r -u "${hint_sender_fd[0]}" hint_sender_output; do - verbose_print "$hint_sender_output" - done - - wait $hint_sender_pid - fi -} - -configure_compiler_filter() { - local the_compiler_filter="$1" - local the_package="$2" - local the_activity="$3" - - if [[ -z $the_compiler_filter ]]; then - verbose_print "No --compiler-filter specified, don't need to force it." - return 0 - fi - - local current_compiler_filter_info="$("$DIR"/query_compiler_filter.py --package "$the_package")" - local res=$? - if [[ $res -ne 0 ]]; then - return $res - fi - - local current_compiler_filter - local current_reason - local current_isa - read current_compiler_filter current_reason current_isa <<< "$current_compiler_filter_info" - - verbose_print "Compiler Filter="$current_compiler_filter "Reason="$current_reason "Isa="$current_isa - - # Don't trust reasons that aren't 'unknown' because that means we didn't manually force the compilation filter. - # (e.g. if any automatic system-triggered compilations are not unknown). - if [[ $current_reason != "unknown" ]] || [[ $current_compiler_filter != $the_compiler_filter ]]; then - verbose_print "$DIR"/force_compiler_filter --compiler-filter "$the_compiler_filter" --package "$the_package" --activity "$the_activity" - "$DIR"/force_compiler_filter --compiler-filter "$the_compiler_filter" --package "$the_package" --activity "$the_activity" - res=$? - else - verbose_print "Queried compiler-filter matched requested compiler-filter, skip forcing." - res=0 - fi - - return $res -} - -# Ensure the APK is currently compiled with whatever we passed in via --compiler-filter. -# No-op if this option was not passed in. -configure_compiler_filter "$compiler_filter" "$package" "$activity" || exit 1 - -# convert 'a=b\nc=d\ne=f\n...' into 'b,d,f,...' -parse_metrics_output_string() { - # single string with newlines in it. - local input="$1" - - local metric_name - local metric_value - local rest - - local all_metrics=() - - # (n1=v1 n2=v2 n3=v3 ...) - readarray -t all_metrics <<< "$input" - - local kv_pair=() - local i - - for i in "${all_metrics[@]}" - do - verbose_print "parse_metrics_output: element '$i'" - # name=value - - IFS='=' read -r metric_name metric_value rest <<< "$i" - - verbose_print "parse_metrics_output: metric_value '$metric_value'" - - # (value1 value2 value3 ...) - all_metrics+=(${metric_value}) - done - - # "value1,value2,value3,..." - join_by ',' "${all_metrics[@]}" -} - -# convert 'a=b\nc=d\ne=f\n... into b,d,f,...' -parse_metrics_output() { - local metric_name - local metric_value - local rest - - local all_metrics=() - - while IFS='=' read -r metric_name metric_value rest; do - verbose_print "metric: $metric_name, value: $metric_value; rest: $rest" - all_metrics+=($metric_value) - done - - join_by ',' "${all_metrics[@]}" -} - -# convert 'a=b\nc=d\ne=f\n... into b,d,f,...' -parse_metrics_header() { - local metric_name - local metric_value - local rest - - local all_metrics=() - - while IFS='=' read -r metric_name metric_value rest; do - verbose_print "metric: $metric_name, value: $metric_value; rest: $rest" - all_metrics+=($metric_name) - done - - join_by ',' "${all_metrics[@]}" -} - -if [[ $report_fully_drawn == y ]]; then - metrics_header="$("$DIR/parse_metrics" --package "$package" --activity "$activity" --simulate --reportfullydrawn | parse_metrics_header)" -else - metrics_header="$("$DIR/parse_metrics" --package "$package" --activity "$activity" --simulate | parse_metrics_header)" -fi - -# TODO: This loop logic could probably be moved into app_startup_runner.py -for ((i=0;i<count;++i)) do - verbose_print "==========================================" - verbose_print "==== ITERATION $i ====" - verbose_print "==========================================" - if [[ $mode != "warm" ]]; then - # The package must be killed **before** we drop caches, otherwise pages will stay resident. - verbose_print "Kill package for non-warm start." - remote_pkill "$package" - verbose_print "Drop caches for non-warm start." - # Drop all caches to get cold starts. - adb shell "echo 3 > /proc/sys/vm/drop_caches" - fi - - perform_aot "$when" "$mode" - - verbose_print "Running with timeout $timeout" - - pre_launch_timestamp="$(logcat_save_timestamp)" - - # TODO: multiple metrics output. - -if [[ $report_fully_drawn == y ]]; then - total_time="$(timeout $timeout "$DIR/launch_application" "$package" "$activity" | "$DIR/parse_metrics" --package "$package" --activity "$activity" --timestamp "$pre_launch_timestamp" --reportfullydrawn | parse_metrics_output)" -else - total_time="$(timeout $timeout "$DIR/launch_application" "$package" "$activity" | "$DIR/parse_metrics" --package "$package" --activity "$activity" --timestamp "$pre_launch_timestamp" | parse_metrics_output)" -fi - - if [[ $? -ne 0 ]]; then - echo "WARNING: Skip bad result, try iteration again." >&2 - ((i=i-1)) - continue - fi - - perform_post_launch_cleanup "$when" "$mode" "$pre_launch_timestamp" - - if [[ $? -ne 0 ]]; then - echo "WARNING: Skip bad cleanup, try iteration again." >&2 - ((i=i-1)) - continue - fi - - echo "Iteration $i. Total time was: $total_time" - - timings_array+=("$total_time") -done - -# drop the first result which is usually garbage. -timings_array=("${timings_array[@]:1}") - -# Print the CSV header first. -echo_to_output_file "$metrics_header" - -# Print out interactive/debugging timings and averages. -# Other scripts should use the --output flag and parse the CSV. -for tim in "${timings_array[@]}"; do - echo_to_output_file "$tim" -done - -if [[ x$output != x ]]; then - echo " Saved results to '$output'" -fi - -if [[ $needs_trace_file == y ]] ; then - iorapd_compiler_purge_trace_file "$package" "$activity" -fi - -# Kill the process to ensure AM isn't keeping it around. -remote_pkill "$package" - -exit 0 diff --git a/startop/scripts/app_startup/run_app_with_prefetch.py b/startop/scripts/app_startup/run_app_with_prefetch.py deleted file mode 100755 index 2f1eff2c41f6..000000000000 --- a/startop/scripts/app_startup/run_app_with_prefetch.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. - -"""Runner of one test given a setting. - -Run app and gather the measurement in a certain configuration. -Print the result to stdout. -See --help for more details. - -Sample usage: - $> ./python run_app_with_prefetch.py -p com.android.settings -a - com.android.settings.Settings -r fadvise -i input - -""" - -import argparse -import os -import sys -import time -from typing import List, Tuple, Optional - -# local imports -import lib.adb_utils as adb_utils -from lib.app_runner import AppRunner, AppRunnerListener - -# global variables -DIR = os.path.abspath(os.path.dirname(__file__)) - -sys.path.append(os.path.dirname(DIR)) -import lib.print_utils as print_utils -import lib.cmd_utils as cmd_utils -import iorap.lib.iorapd_utils as iorapd_utils - -class PrefetchAppRunner(AppRunnerListener): - def __init__(self, - package: str, - activity: Optional[str], - readahead: str, - compiler_filter: Optional[str], - timeout: Optional[int], - simulate: bool, - debug: bool, - input:Optional[str], - **kwargs): - self.app_runner = AppRunner(package, - activity, - compiler_filter, - timeout, - simulate) - self.app_runner.add_callbacks(self) - - self.simulate = simulate - self.readahead = readahead - self.debug = debug - self.input = input - print_utils.DEBUG = self.debug - cmd_utils.SIMULATE = self.simulate - - - def run(self) -> Optional[List[Tuple[str]]]: - """Runs an app. - - Returns: - A list of (metric, value) tuples. - """ - return self.app_runner.run() - - def preprocess(self): - passed = self.validate_options() - if not passed: - return - - # Sets up adb environment. - adb_utils.root() - adb_utils.disable_selinux() - time.sleep(1) - - # Kill any existing process of this app - adb_utils.pkill(self.app_runner.package) - - if self.readahead != 'warm': - print_utils.debug_print('Drop caches for non-warm start.') - # Drop all caches to get cold starts. - adb_utils.vm_drop_cache() - - if self.readahead != 'warm' and self.readahead != 'cold': - iorapd_utils.enable_iorapd_readahead() - - def postprocess(self, pre_launch_timestamp: str): - passed = self._perform_post_launch_cleanup(pre_launch_timestamp) - if not passed and not self.app_runner.simulate: - print_utils.error_print('Cannot perform post launch cleanup!') - return None - - # Kill any existing process of this app - adb_utils.pkill(self.app_runner.package) - - def _perform_post_launch_cleanup(self, logcat_timestamp: str) -> bool: - """Performs cleanup at the end of each loop iteration. - - Returns: - A bool indicates whether the cleanup succeeds or not. - """ - if self.readahead != 'warm' and self.readahead != 'cold': - passed = iorapd_utils.wait_for_iorapd_finish(self.app_runner.package, - self.app_runner.activity, - self.app_runner.timeout, - self.debug, - logcat_timestamp) - - if not passed: - return passed - - return iorapd_utils.disable_iorapd_readahead() - - # Don't need to do anything for warm or cold. - return True - - def metrics_selector(self, am_start_output: str, - pre_launch_timestamp: str) -> str: - """Parses the metric after app startup by reading from logcat in a blocking - manner until all metrics have been found". - - Returns: - the total time and displayed time of app startup. - For example: "TotalTime=123\nDisplayedTime=121 - """ - total_time = AppRunner.parse_total_time(am_start_output) - displayed_time = adb_utils.blocking_wait_for_logcat_displayed_time( - pre_launch_timestamp, self.app_runner.package, self.app_runner.timeout) - - return 'TotalTime={}\nDisplayedTime={}'.format(total_time, displayed_time) - - def validate_options(self) -> bool: - """Validates the activity and trace file if needed. - - Returns: - A bool indicates whether the activity is valid. - """ - needs_trace_file = self.readahead != 'cold' and self.readahead != 'warm' - if needs_trace_file and (self.input is None or - not os.path.exists(self.input)): - print_utils.error_print('--input not specified!') - return False - - # Install necessary trace file. This must be after the activity checking. - if needs_trace_file: - passed = iorapd_utils.iorapd_compiler_install_trace_file( - self.app_runner.package, self.app_runner.activity, self.input) - if not cmd_utils.SIMULATE and not passed: - print_utils.error_print('Failed to install compiled TraceFile.pb for ' - '"{}/{}"'. - format(self.app_runner.package, - self.app_runner.activity)) - return False - - return True - - - -def parse_options(argv: List[str] = None): - """Parses command line arguments and return an argparse Namespace object.""" - parser = argparse.ArgumentParser( - description='Run an Android application once and measure startup time.' - ) - - required_named = parser.add_argument_group('required named arguments') - required_named.add_argument('-p', '--package', action='store', dest='package', - help='package of the application', required=True) - - # optional arguments - # use a group here to get the required arguments to appear 'above' the - # optional arguments in help. - optional_named = parser.add_argument_group('optional named arguments') - optional_named.add_argument('-a', '--activity', action='store', - dest='activity', - help='launch activity of the application') - optional_named.add_argument('-s', '--simulate', dest='simulate', - action='store_true', - help='simulate the process without executing ' - 'any shell commands') - optional_named.add_argument('-d', '--debug', dest='debug', - action='store_true', - help='Add extra debugging output') - optional_named.add_argument('-i', '--input', action='store', dest='input', - help='perfetto trace file protobuf', - default='TraceFile.pb') - optional_named.add_argument('-r', '--readahead', action='store', - dest='readahead', - help='which readahead mode to use', - default='cold', - choices=('warm', 'cold', 'mlock', 'fadvise')) - optional_named.add_argument('-t', '--timeout', dest='timeout', action='store', - type=int, - help='Timeout after this many seconds when ' - 'executing a single run.', - default=10) - optional_named.add_argument('--compiler-filter', dest='compiler_filter', - action='store', - help='Which compiler filter to use.', - default=None) - - return parser.parse_args(argv) - -def main(): - opts = parse_options() - runner = PrefetchAppRunner(**vars(opts)) - result = runner.run() - - if result is None: - return 1 - - print(result) - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/startop/scripts/app_startup/run_app_with_prefetch_test.py b/startop/scripts/app_startup/run_app_with_prefetch_test.py deleted file mode 100644 index 8a588e4463e9..000000000000 --- a/startop/scripts/app_startup/run_app_with_prefetch_test.py +++ /dev/null @@ -1,286 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. -# -"""Unit tests for the run_app_with_prefetch_test.py script. - -Install: - $> sudo apt-get install python3-pytest ## OR - $> pip install -U pytest -See also https://docs.pytest.org/en/latest/getting-started.html - -Usage: - $> ./run_app_with_prefetch_test.py - $> pytest run_app_with_prefetch_test.py - $> python -m pytest run_app_with_prefetch_test.py - -See also https://docs.pytest.org/en/latest/usage.html -""" - -import io -import os -import shlex -import sys -import tempfile -# global imports -from contextlib import contextmanager - -# pip imports -import pytest -# local imports -import run_app_with_prefetch as runner -from mock import call, patch, Mock - -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from app_startup.lib.app_runner import AppRunner -# -# Argument Parsing Helpers -# - -@contextmanager -def ignore_stdout_stderr(): - """Ignore stdout/stderr output for duration of this context.""" - old_stdout = sys.stdout - old_stderr = sys.stderr - sys.stdout = io.StringIO() - sys.stderr = io.StringIO() - try: - yield - finally: - sys.stdout = old_stdout - sys.stderr = old_stderr - -@contextmanager -def argparse_bad_argument(msg): - """Asserts that a SystemExit is raised when executing this context. - - If the assertion fails, print the message 'msg'. - """ - with pytest.raises(SystemExit, message=msg): - with ignore_stdout_stderr(): - yield - -def assert_bad_argument(args, msg): - """Asserts that the command line arguments in 'args' are malformed. - - Prints 'msg' if the assertion fails. - """ - with argparse_bad_argument(msg): - parse_args(args) - -def parse_args(args): - """ - :param args: command-line like arguments as a single string - :return: dictionary of parsed key/values - """ - # "-a b -c d" => ['-a', 'b', '-c', 'd'] - return vars(runner.parse_options(shlex.split(args))) - -def default_dict_for_parsed_args(**kwargs): - """Combines it with all of the "optional" parameters' default values.""" - d = { - 'readahead': 'cold', - 'simulate': None, - 'simulate': False, - 'debug': False, - 'input': 'TraceFile.pb', - 'timeout': 10, - 'compiler_filter': None, - 'activity': None - } - d.update(kwargs) - return d - -def default_mock_dict_for_parsed_args(include_optional=True, **kwargs): - """Combines default dict with all optional parameters with some mock required - parameters. - """ - d = {'package': 'com.fake.package'} - if include_optional: - d.update(default_dict_for_parsed_args()) - d.update(kwargs) - return d - -def parse_optional_args(str): - """ - Parses an argument string which already includes all the required arguments - in default_mock_dict_for_parsed_args. - """ - req = '--package com.fake.package' - return parse_args('%s %s' % (req, str)) - -def test_argparse(): - # missing arguments - assert_bad_argument('', '-p are required') - - # required arguments are parsed correctly - ad = default_dict_for_parsed_args # assert dict - assert parse_args('--package xyz') == ad(package='xyz') - - assert parse_args('-p xyz') == ad(package='xyz') - - assert parse_args('-p xyz -s') == ad(package='xyz', simulate=True) - assert parse_args('-p xyz --simulate') == ad(package='xyz', simulate=True) - - # optional arguments are parsed correctly. - mad = default_mock_dict_for_parsed_args # mock assert dict - assert parse_optional_args('--input trace.pb') == mad(input='trace.pb') - - assert parse_optional_args('--compiler-filter speed') == \ - mad(compiler_filter='speed') - - assert parse_optional_args('-d') == mad(debug=True) - assert parse_optional_args('--debug') == mad(debug=True) - - assert parse_optional_args('--timeout 123') == mad(timeout=123) - assert parse_optional_args('-t 456') == mad(timeout=456) - - assert parse_optional_args('-r warm') == mad(readahead='warm') - assert parse_optional_args('--readahead warm') == mad(readahead='warm') - - assert parse_optional_args('-a act') == mad(activity='act') - assert parse_optional_args('--activity act') == mad(activity='act') - -def test_main(): - args = '--package com.fake.package --activity act -s' - opts = runner.parse_options(shlex.split(args)) - result = runner.PrefetchAppRunner(**vars(opts)).run() - assert result == [('TotalTime', '123')] - -def _mocked_run_shell_command(*args, **kwargs): - if args[0] == 'adb shell ps | grep "music" | awk \'{print $2;}\'': - return (True, '9999') - else: - return (True, '') - -def test_preprocess_no_cache_drop(): - with patch('lib.cmd_utils.run_shell_command', - new_callable=Mock) as mock_run_shell_command: - mock_run_shell_command.side_effect = _mocked_run_shell_command - prefetch_app_runner = runner.PrefetchAppRunner(package='music', - activity='MainActivity', - readahead='warm', - compiler_filter=None, - timeout=None, - simulate=False, - debug=False, - input=None) - - prefetch_app_runner.preprocess() - - calls = [call('adb root'), - call('adb shell "getenforce"'), - call('adb shell "setenforce 0"'), - call('adb shell "stop"'), - call('adb shell "start"'), - call('adb wait-for-device'), - call('adb shell ps | grep "music" | awk \'{print $2;}\''), - call('adb shell "kill 9999"')] - mock_run_shell_command.assert_has_calls(calls) - -def test_preprocess_with_cache_drop(): - with patch('lib.cmd_utils.run_shell_command', - new_callable=Mock) as mock_run_shell_command: - mock_run_shell_command.side_effect = _mocked_run_shell_command - prefetch_app_runner = runner.PrefetchAppRunner(package='music', - activity='MainActivity', - readahead='cold', - compiler_filter=None, - timeout=None, - simulate=False, - debug=False, - input=None) - - prefetch_app_runner.preprocess() - - calls = [call('adb root'), - call('adb shell "getenforce"'), - call('adb shell "setenforce 0"'), - call('adb shell "stop"'), - call('adb shell "start"'), - call('adb wait-for-device'), - call('adb shell ps | grep "music" | awk \'{print $2;}\''), - call('adb shell "kill 9999"'), - call('adb shell "echo 3 > /proc/sys/vm/drop_caches"')] - mock_run_shell_command.assert_has_calls(calls) - -def test_preprocess_with_cache_drop_and_iorapd_enabled(): - with patch('lib.cmd_utils.run_shell_command', - new_callable=Mock) as mock_run_shell_command: - mock_run_shell_command.side_effect = _mocked_run_shell_command - - with tempfile.NamedTemporaryFile() as input: - prefetch_app_runner = runner.PrefetchAppRunner(package='music', - activity='MainActivity', - readahead='fadvise', - compiler_filter=None, - timeout=None, - simulate=False, - debug=False, - input=input.name) - - prefetch_app_runner.preprocess() - - calls = [call('adb root'), - call('adb shell "getenforce"'), - call('adb shell "setenforce 0"'), - call('adb shell "stop"'), - call('adb shell "start"'), - call('adb wait-for-device'), - call( - 'adb shell ps | grep "music" | awk \'{print $2;}\''), - call('adb shell "kill 9999"'), - call('adb shell "echo 3 > /proc/sys/vm/drop_caches"'), - call('bash -c "source {}; iorapd_readahead_enable"'. - format(AppRunner.IORAP_COMMON_BASH_SCRIPT))] - mock_run_shell_command.assert_has_calls(calls) - -@patch('lib.adb_utils.blocking_wait_for_logcat_displayed_time') -@patch('lib.cmd_utils.run_shell_command') -def test_postprocess_with_launch_cleanup( - mock_run_shell_command, - mock_blocking_wait_for_logcat_displayed_time): - mock_run_shell_command.side_effect = _mocked_run_shell_command - mock_blocking_wait_for_logcat_displayed_time.return_value = 123 - - with tempfile.NamedTemporaryFile() as input: - prefetch_app_runner = runner.PrefetchAppRunner(package='music', - activity='MainActivity', - readahead='fadvise', - compiler_filter=None, - timeout=10, - simulate=False, - debug=False, - input=input.name) - - prefetch_app_runner.postprocess('2019-07-02 23:20:06.972674825') - - calls = [ - call('bash -c "source {script_path}; ' - 'iorapd_readahead_wait_until_finished ' - '\'{package}\' \'{activity}\' \'{timestamp}\' \'{timeout}\'"'. - format(timeout=10, - package='music', - activity='MainActivity', - timestamp='2019-07-02 23:20:06.972674825', - script_path=AppRunner.IORAP_COMMON_BASH_SCRIPT)), - call('bash -c "source {}; iorapd_readahead_disable"'. - format(AppRunner.IORAP_COMMON_BASH_SCRIPT)), - call('adb shell ps | grep "music" | awk \'{print $2;}\''), - call('adb shell "kill 9999"')] - mock_run_shell_command.assert_has_calls(calls) - -if __name__ == '__main__': - pytest.main() diff --git a/startop/scripts/app_startup/unlock_screen b/startop/scripts/app_startup/unlock_screen deleted file mode 100755 index 478294c9f35d..000000000000 --- a/startop/scripts/app_startup/unlock_screen +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# -# Copyright 2018, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This turns the screen on if it's off. -# If it's on it does nothing unless its on the home screen, in which case it opens up some background -# menu. -# -# However, this menu is ignored because "am start" commands still work as expected. -adb shell input keyevent MENU diff --git a/startop/scripts/iorap/analyze_prefetch_file.py b/startop/scripts/iorap/analyze_prefetch_file.py deleted file mode 100755 index 343cd54b7174..000000000000 --- a/startop/scripts/iorap/analyze_prefetch_file.py +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2020, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import os -import sys -from typing import Dict, List, NamedTuple, Tuple - -DIR = os.path.abspath(os.path.dirname(__file__)) -sys.path.append(os.path.dirname(DIR)) # framework/base/startop/script -import lib.print_utils as print_utils - -# Include generated protos. -dir_name = os.path.dirname(os.path.realpath(__file__)) -sys.path.append(dir_name + "/generated") - -from TraceFile_pb2 import * - -def parse_options(argv: List[str] = None): - """Parses command line arguments and returns an argparse Namespace object.""" - parser = argparse.ArgumentParser(description="Analyze compiled_trace iorap protos.") - required_named = parser.add_argument_group('required named arguments') - - required_named.add_argument('-i', dest='input', metavar='FILE', - help='Read protobuf file as input') - - optional_named = parser.add_argument_group('optional named arguments') - - optional_named.add_argument('-up', dest='upper_percent', type=float, - default=95.0, - help='Only show the top-most entries up to this value.') - - optional_named.add_argument('-r', dest='raw', action='store_true', - help='Output entire raw file.') - optional_named.add_argument('-o', dest='output', - help='The results are stored into the output file') - optional_named.add_argument('-d', dest='debug', action='store_true' - , help='Activity of the app to be compiled') - - return parser.parse_args(argv) - -def open_iorap_prefetch_file(file_path: str) -> TraceFile: - with open(file_path, "rb") as f: - tf = TraceFile() - tf.ParseFromString(f.read()) - return tf - -def print_stats_summary(trace_file: TraceFile, upper_percent): - tf_dict = convert_to_dict(trace_file) - print_utils.debug_print(tf_dict) - - total_length = 0 - summaries = [] - for name, entries_list in tf_dict.items(): - summary = entries_sum(entries_list) - summaries.append(summary) - - total_length += summary.length - - # Sort by length - summaries.sort(reverse=True, key=lambda s: s.length) - - percent_sum = 0.0 - skipped_entries = 0 - - print("===========================================") - print("Total length: {:,} bytes".format(total_length)) - print("Displayed upper percent: {:0.2f}%".format(upper_percent)) - print("===========================================") - print("") - print("name,length,percent_of_total,upper_percent") - for sum in summaries: - percent_of_total = (sum.length * 1.0) / (total_length * 1.0) * 100.0 - - percent_sum += percent_of_total - - if percent_sum > upper_percent: - skipped_entries = skipped_entries + 1 - continue - - #print("%s,%d,%.2f%%" %(sum.name, sum.length, percent_of_total)) - print("{:s},{:d},{:0.2f}%,{:0.2f}%".format(sum.name, sum.length, percent_of_total, percent_sum)) - - if skipped_entries > 0: - print("[WARNING] Skipped {:d} entries, use -up=100 to show everything".format(skipped_entries)) - - pass - -class FileEntry(NamedTuple): - id: int - name: str - offset: int - length: int - -class FileEntrySummary(NamedTuple): - name: str - length: int - -def entries_sum(entries: List[FileEntry]) -> FileEntrySummary: - if not entries: - return None - - summary = FileEntrySummary(name=entries[0].name, length=0) - for entry in entries: - summary = FileEntrySummary(summary.name, summary.length + entry.length) - - return summary - -def convert_to_dict(trace_file: TraceFile) -> Dict[str, FileEntry]: - trace_file_index = trace_file.index - - # entries.id -> entry.file_name - entries_map = {} - - index_entries = trace_file_index.entries - for entry in index_entries: - entries_map[entry.id] = entry.file_name - - final_map = {} - - file_entries_map = {} - file_entries = trace_file.list.entries - for entry in file_entries: - print_utils.debug_print(entry) - - lst = file_entries_map.get(entry.index_id, []) - file_entries_map[entry.index_id] = lst - - file_name = entries_map[entry.index_id] - file_entry = \ - FileEntry(id=entry.index_id, name=file_name, offset=entry.file_offset, length=entry.file_length) - - lst.append(file_entry) - - final_map[file_name] = lst - - return final_map - -def main(argv: List[str]) -> int: - opts = parse_options(argv[1:]) - if opts.debug: - print_utils.DEBUG = opts.debug - print_utils.debug_print(opts) - - prefetch_file = open_iorap_prefetch_file(opts.input) - - if opts.raw: - print(prefetch_file) - - print_stats_summary(prefetch_file, opts.upper_percent) - - return 0 - -if __name__ == '__main__': - sys.exit(main(sys.argv)) diff --git a/startop/scripts/iorap/collector b/startop/scripts/iorap/collector deleted file mode 100755 index 3dc080a5ac9c..000000000000 --- a/startop/scripts/iorap/collector +++ /dev/null @@ -1,403 +0,0 @@ -#!/bin/bash -# -# Copyright 2017, The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -APP_STARTUP_DIR="$DIR/../app_startup/" -source "$DIR/common" - -usage() { - cat <<EOF -Usage: collector [OPTIONS]... - -Runs an application, causes an iorap trace to be collected for it, and then invokes the iorap -compiler to generate a TraceFile.pb. - - -p, --package package of the app to test - -a, --activity activity of the app to test - -h, --help usage information (this) - -v, --verbose enable extra verbose printing - -i, --inodes path to inodes file (system/extras/pagecache/pagecache.py -d inodes) - -b, --trace_buffer_size how big to make trace buffer size (default 32768) - -w, --wait_time how long to run systrace for (default 10) in seconds - -c, --compiler-filter override the compilation filter if set (default none) - -o, --output output trace file protobuf (default 'TraceFile.pb') -EOF -} - - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -trace_buffer_size=32768 -wait_time=10 -comp_filter="" -output_dest="TraceFile.pb" - -parse_arguments() { - while [[ $# -gt 0 ]]; do - case "$1" in - -a|--activity) - activity="$2" - shift - ;; - -h|--help) - usage - exit 0 - ;; - -p|--package) - package="$2" - shift - ;; - -i|--inodes) - inodes="$2" - shift - ;; - -b|--trace_buffer_size) - trace_buffer_size="$2" - shift - ;; - -w|--wait_time) - wait_time="$2" - shift - ;; - -c|--compiler-filter) - comp_filter="$2" - shift - ;; - -o|--output) - output_dest="$2" - shift - ;; - -v|--verbose) - verbose="y" - ;; - esac - shift - done -} - -remote_pidof() { - local procname="$1" - adb shell ps | grep "$procname" | awk '{print $2;}' -} - -remote_pkill() { - local procname="$1" - shift - - local the_pids=$(remote_pidof "$procname") - local pid - - for pid in $the_pids; do - verbose_print adb shell kill "$@" "$pid" - adb shell kill "$@" "$pid" - done -} - -force_package_compilation() { - local arg_comp_filter="$1" - local arg_package="$2" - - if [[ $arg_comp_filter == speed-profile ]]; then - # Force the running app to dump its profiles to disk. - remote_pkill "$arg_package" -SIGUSR1 - sleep 1 # give some time for above to complete. - fi - - adb shell cmd package compile -m "$arg_comp_filter" -f "$arg_package" -} - -parse_package_dumpsys_line() { - local what_left="$1" - local what_right="$2" - local line="$3" - - if [[ $line == *${what_left}*${what_right}* ]]; then - found="${line#*$what_left}" - found="${found%$what_right*}" - echo "$found" - return 0 - fi - - return 1 -} - -parse_package_dumpsys_section() { - local what_left="$1" - local what_right="$2" - shift - local lines="$@" - - lines="${lines//$'\n'/}" - - local new_lines=() - - local current_line="" - local newline=n - local line - for line in "${lines[@]}"; do - if [[ $line == *: ]]; then - newline=y - current_line="" - new_lines+=("$current_line") - - parse_package_dumpsys_line "$what_left" "$what_right" "$current_line" && return 0 - else - # strip all spaces from the start - line="${line//$' '/}" - current_line+="$line" - #prepend to current line - fi - done - [[ "$current_line" != "" ]] && new_lines+=("$current_line") - - parse_package_dumpsys_line "$what_left" "$what_right" "$current_line" && return 0 - - return 1 -} - -parse_package_compilation() { - local pkg="$1" -# [com.google.android.apps.maps] - - local compilation_filter - local is_prebuilt - local isa - local etc - - local ret_code - - read compilation_filter is_prebuilt isa etc <<< "$("$APP_STARTUP_DIR"/query_compiler_filter.py --package "$pkg")" - ret_code=$? - - if [[ $ret_code -eq 0 && x$compilation_filter != x ]]; then - verbose_print "Package compilation info for $pkg was '$compilation_filter'" - echo "$compilation_filter" - return 0 - else - verbose_print "query failed ret code $ret_code filter=$compilation_filter" - fi - - return $ret_code -} - -# Main entry point -if [[ $# -eq 0 ]]; then - usage - exit 1 -else - parse_arguments "$@" - - # if we do not have have package exit early with an error - [[ "$package" == "" ]] && echo "--package not specified" 1>&2 && exit 1 - - if [[ -z "$inodes" ]] || ! [[ -f $inodes ]]; then - echo "--inodes not specified" 1>&2 - exit 1 - fi - - if [[ "$activity" == "" ]]; then - activity="$(get_activity_name "$package")" - if [[ "$activity" == "" ]]; then - echo "Activity name could not be found, invalid package name?" 1>&2 - exit 1 - else - verbose_print "Activity name inferred: " "$activity" - fi - fi -fi - -adb root > /dev/null - -if [[ "$(adb shell getenforce)" != "Permissive" ]]; then - adb shell setenforce 0 - adb shell stop - adb shell start - adb wait-for-device -fi - -compilation_was="$(parse_package_compilation "$package")" -if [[ $? -ne 0 ]]; then - echo "Could not determine package compilation filter; was this package installed?" >&2 - exit 1 -fi -verbose_print "Package compilation: $compilation_was" - -# Cannot downgrade (e.g. from speed-profile to quicken) without forceful recompilation. -# Forceful recompilation will recompile even if compilation filter was unchanged. -# Therefore avoid recompiling unless the filter is actually different than what we asked for. -if [[ "x$comp_filter" != "x" ]] && [[ "$compilation_was" != "$comp_filter" ]]; then - echo "Current compilation filter is '$compilation_was'; force recompile to '$comp_filter'" >&2 - #TODO: this matching seems hopelessly broken, it will always recompile. - - force_package_compilation "$comp_filter" "$package" -fi - -# Drop all caches prior to beginning a systrace, otherwise we won't record anything already in pagecache. -adb shell "echo 3 > /proc/sys/vm/drop_caches" - -trace_tmp_file="$(mktemp -t trace.XXXXXXXXX.html)" - -function finish { - [[ -f "$trace_tmp_file" ]] && rm "$trace_tmp_file" -} -trap finish EXIT - -launch_application_and_wait_for_trace() { - local package="$1" - local activity="$2" - local timeout=30 # seconds - - # Ensure application isn't running already. - remote_pkill "$package" - - # 5 second trace of Home screen causes - # a trace of the home screen. - # There is no way to abort the trace - # so just wait for it to complete instead. - sleep 30 - - local time_now="$(logcat_save_timestamp)" - local retcode=0 - - verbose_print "Drop caches for non-warm start." - # Drop all caches to get cold starts. - adb shell "echo 3 > /proc/sys/vm/drop_caches" - - verbose_print "now launching application" - # Launch an application - "$APP_STARTUP_DIR"/launch_application "$package" "$activity" - retcode=$? - if [[ $retcode -ne 0 ]]; then - echo "FATAL: Application launch failed." >&2 - return $retcode - fi - - # This blocks until 'am start' returns at which point the application is - # already to be considered "started" as the first frame has been drawn. - - # TODO: check for cold start w.r.t to activitymanager? - - # Wait for application to start from the point of view of ActivityTaskManager. - local pattern="ActivityTaskManager: Displayed $package" - logcat_wait_for_pattern "$timeout" "$time_now" "$pattern" - retcode=$? - if [[ $retcode -ne 0 ]]; then - echo "FATAL: Could not find '$pattern' in logcat." >&2 - return $retcode - fi - - # Wait for iorapd to finish writing out the perfetto traces for this app. - iorapd_perfetto_wait_for_app_trace "$package" "$activity" "$timeout" "$time_now" - retcode=$? - if [[ $retcode -ne 0 ]]; then - echo "FATAL: Could not save perfetto app trace file." >&2 - return $retcode - fi - - verbose_print "iorapd has finished collecting app trace file for $package/$activity" -} - -collector_main() { - # don't even bother trying to run anything until the screen is unlocked. - "$APP_STARTUP_DIR"/unlock_screen - - # Don't mutate state while iorapd is running. - iorapd_stop || return $? - - # Remove all existing metadata for a package/activity in iorapd. - iorapd_perfetto_purge_app_trace "$package" "$activity" || return $? - iorapd_compiler_purge_trace_file "$package" "$activity" || return $? - - iorapd_perfetto_enable || return $? - iorapd_readahead_disable || return $? - iorapd_start || return $? - - # Wait for perfetto trace to finished writing itself out. - launch_application_and_wait_for_trace "$package" "$activity" || return $? - - # Pull the perfetto trace for manual inspection. - iorapd_perfetto_pull_trace_file "$package" "$activity" "perfetto_trace.pb" - - # Compile the trace so that the next app run can use prefetching. - iorapd_compiler_for_app_trace "$package" "$activity" "$inodes" || return $? - - # Save TraceFile.pb to local file. - iorapd_compiler_pull_trace_file "$package" "$activity" "$output_dest" || return $? - # Remove the TraceFile.pb from the device. - iorapd_compiler_purge_trace_file "$package" "$activity" || return $? - - # TODO: better transactional support for restoring iorapd global properties - iorapd_perfetto_disable || return $? -} - -collector_main "$@" - -verbose_print "Collector finished. Children: " -if [[ $verbose == y ]]; then - jobs -p - ps f -g$$ -fi - -exit $? - - -verbose_print "About to begin systrace" -coproc systrace_fd { - # Disable stdout buffering since we need to know the output of systrace RIGHT AWAY. - stdbuf -oL "$ANDROID_BUILD_TOP"/external/chromium-trace/systrace.py --target=android -b "$trace_buffer_size" -t "$wait_time" am pagecache dalvik -o "$trace_tmp_file" -} - -verbose_print "Systrace began" - -systrace_pid="$!" - -while read -r -u "${systrace_fd[0]}" systrace_output; do - verbose_print "$systrace_output" - if [[ "$systrace_output" == *"Starting tracing"* ]]; then - verbose_print "WE DID SEE STARTING TRACING." - break - fi -done -# Systrace has begun recording the tracing. -# Run the application and collect the results. - -am_output="$(adb shell am start -S -W "$package"/"$activity")" -if [[ $? -ne 0 ]]; then - echo "am start failed" >&2 - - exit 1 -fi - -verbose_print "$am_output" -total_time="$(echo "$am_output" | grep 'TotalTime:' | sed 's/TotalTime: //g')" -verbose_print "total time: $total_time" - -# Now wait for systrace to finish. - -wait "$systrace_pid" || { echo "Systrace finished before am start was finished, try a longer --wait_time"; exit 1; } -verbose_print "Systrace has now finished" -verbose_print "$(ls -la "$trace_tmp_file")" - - -iorapd_perfetto_disable - -# Now that systrace has finished, convert the trace file html file to a protobuf. - -"$ANDROID_BUILD_TOP"/system/iorap/src/py/collector/trace_parser.py -i "$inodes" -t "$trace_tmp_file" -o "$output_dest" || exit 1 - -echo "Trace file collection complete, trace file saved to \"$output_dest\"!" >&2 - -finish diff --git a/startop/scripts/iorap/common b/startop/scripts/iorap/common deleted file mode 100755 index 387e45d431bd..000000000000 --- a/startop/scripts/iorap/common +++ /dev/null @@ -1,253 +0,0 @@ -#!/bin/bash -# -# 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. - -DIR_IORAP_COMMON="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -APP_STARTUP_DIR="$DIR_IORAP_COMMON/../app_startup/" -source "$APP_STARTUP_DIR/lib/common" - -IORAPD_DATA_PATH="/data/misc/iorapd" - -iorapd_start() { - verbose_print 'iorapd_start' - adb shell start iorapd - sleep 1 - # TODO: block until logcat prints successfully connecting -} - -iorapd_stop() { - verbose_print 'iorapd_stop' - adb shell stop iorapd -} - -iorapd_reset() { - iorapd_stop - iorapd_start -} - -# Enable perfetto tracing. -# Subsequent launches of an application will record a perfetto trace protobuf. -iorapd_perfetto_enable() { - verbose_print 'enable perfetto' - adb shell setprop iorapd.perfetto.enable true - iorapd_reset # iorapd only reads this flag when initializing -} - -# Disable perfetto tracing. -# Subsequent launches of applications will no longer record perfetto trace protobufs. -iorapd_perfetto_disable() { - verbose_print 'disable perfetto' - adb shell setprop iorapd.perfetto.enable false - iorapd_reset # iorapd only reads this flag when initializing -} - -# Enable readahead -# Subsequent launches of an application will be sped up by iorapd readahead prefetching -# (Provided an appropriate compiled trace exists for that application) -iorapd_readahead_enable() { - if [[ "$(adb shell getprop iorapd.readahead.enable)" == true ]]; then - verbose_print 'enable readahead [already enabled]' - return 0 - fi - verbose_print 'enable readahead [reset iorapd]' - adb shell setprop iorapd.readahead.enable true - iorapd_reset # iorapd only reads this flag when initializing -} - -# Disable readahead -# Subsequent launches of an application will be not be sped up by iorapd readahead prefetching. -iorapd_readahead_disable() { - if [[ "$(adb shell getprop iorapd.readahead.enable)" == false ]]; then - verbose_print 'disable readahead [already disabled]' - return 0 - fi - verbose_print 'disable readahead [reset iorapd]' - adb shell setprop iorapd.readahead.enable false - iorapd_reset # iorapd only reads this flag when initializing -} - -_iorapd_path_to_data_file() { - local package="$1" - local activity="$2" - local suffix="$3" - - # Match logic of 'AppComponentName' in iorap::compiler C++ code. - echo "${IORAPD_DATA_PATH}/${package}%2F${activity}.${suffix}" -} - -iorapd_perfetto_wait_for_app_trace() { - local package="$1" - local activity="$2" - local timeout="$3" - local timestamp="$4" - - local remote_path="$(_iorapd_path_to_data_file "$package" "$activity" "perfetto_trace.pb")" - - verbose_print "iorapd_perfetto_wait_for_app_trace on file '$remote_path'" - - # see event_manager.cc - local pattern="Perfetto TraceBuffer saved to file: $remote_path" - logcat_wait_for_pattern "$timeout" "$timestamp" "$pattern" -} - -# Purge all perfetto traces for a given application. -iorapd_perfetto_purge_app_trace() { - local package="$1" - local activity="$2" - - local remote_path="$(_iorapd_path_to_data_file "$package" "$activity" "perfetto_trace.pb")" - - verbose_print 'iorapd-perfetto: purge app trace in ' "$remote_path" - adb shell "[[ -f '$remote_path' ]] && rm -f '$remote_path' || exit 0" -} - -# Pull the remote perfetto trace file into a local file. -iorapd_perfetto_pull_trace_file() { - local package="$1" - local activity="$2" - local output_file="$3" # local path - - local compiled_path="$(_iorapd_path_to_data_file "$package" "$activity" "perfetto_trace.pb")" - - if ! adb shell "[[ -f '$compiled_path' ]]"; then - echo "Error: Remote path '$compiled_path' invalid" >&2 - return 1 - fi - if ! mkdir -p "$(dirname "$output_file")"; then - echo "Error: Fail to make output directory for '$output_file'" >&2 - return 1 - fi - verbose_print adb pull "$compiled_path" "$output_file" - adb pull "$compiled_path" "$output_file" -} - -# Compile a perfetto trace for a given application. -# This requires the app has run at least once with perfetto tracing enabled. -iorapd_compiler_for_app_trace() { - local package="$1" - local activity="$2" - local inodes="$3" # local path - - # remote path calculations - local input_path="$(_iorapd_path_to_data_file "$package" "$activity" "perfetto_trace.pb")" - local compiled_path="$(_iorapd_path_to_data_file "$package" "$activity" "compiled_trace.tmp.pb")" - local compiled_path_final="$(_iorapd_path_to_data_file "$package" "$activity" "compiled_trace.pb")" - - if ! adb shell "[[ -f '$input_path' ]]"; then - echo "Error: Missing perfetto traces; nothing to compile. Expected: '$input_path'" >&2 - return 1 - fi - - if ! [[ -f $inodes ]]; then - # We could compile using 'diskscan' but it's non-deterministic, so refuse instead. - echo "Error: Missing inodes textcache at '$inodes'; refusing to compile." >&2 - return 1 - fi - - # inodes file needs to be on the device for iorap.cmd.compiler to access it - local remote_inodes=/data/local/tmp/prefetch/inodes.txt - adb shell "mkdir -p \"$(dirname "$remote_inodes")\"" || return 1 - verbose_print adb push "$inodes" "$remote_inodes" - adb push "$inodes" "$remote_inodes" - - verbose_print 'iorapd-compiler: compile app trace in ' "$input_path" - verbose_print adb shell "iorap.cmd.compiler '$input_path' --inode-textcache '$remote_inodes' --output-proto '$compiled_path'" - adb shell "iorap.cmd.compiler '$input_path' --inode-textcache '$remote_inodes' --output-proto '$compiled_path'" - retcode=$? - - # Don't overwrite the true 'compiled_trace.pb' unless the compiler completed without error. - # TODO: The native compiler code should be handling its own transaction-safety. - if [[ $retcode -eq 0 ]]; then - adb shell "mv '$compiled_path' '$compiled_path_final'" - else - adb shell "[[ -f '$compiled_path' ]] && rm -f '$compiled_path'" - fi - - # Clean up inodes file we just pushed. -# adb shell "[[ -f '$remote_inodes' ]] && rm -f '$remote_inodes'" - - return $retcode -} - -# Pull the remote compiled trace file into a local file. -iorapd_compiler_pull_trace_file() { - local package="$1" - local activity="$2" - local output_file="$3" # local path - - local compiled_path="$(_iorapd_path_to_data_file "$package" "$activity" "compiled_trace.pb")" - - if ! adb shell "[[ -f '$compiled_path' ]]"; then - echo "Error: Remote path '$compiled_path' invalid" >&2 - return 1 - fi - if ! mkdir -p "$(dirname "$output_file")"; then - echo "Error: Fail to make output directory for '$output_file'" >&2 - return 1 - fi - verbose_print adb pull "$compiled_path" "$output_file" - adb pull "$compiled_path" "$output_file" -} - -# Install a compiled trace file. -iorapd_compiler_install_trace_file() { - local package="$1" - local activity="$2" - local input_file="$3" # local path - - # remote path calculations - local compiled_path="$(_iorapd_path_to_data_file "$package" "$activity" "compiled_trace.pb")" - - if ! [[ -f $input_file ]]; then - echo "Error: File '$input_file' does not exist." >&2 - return 1 - fi - - adb shell "mkdir -p \"$(dirname "$compiled_path")\"" || return 1 - - verbose_print adb push "$input_file" "$compiled_path" - adb push "$input_file" "$compiled_path" -} - -iorapd_compiler_purge_trace_file() { - local package="$1" - local activity="$2" - local input_file="$3" # local path - - local remote_path="$(_iorapd_path_to_data_file "$package" "$activity" "compiled_trace.pb")" - - adb shell "[[ -f '$remote_path' ]] && rm -f '$remote_path' || exit 0" -} - -# Blocks until the readahead for the requested package/activity has finished. -# This assumes that the trace file was already installed, and also that -# the application launched but not completed yet. -iorapd_readahead_wait_until_finished() { - local package="$1" - local activity="$2" - local timestamp="$3" - local timeout="$4" - - if [[ $# -lt 4 ]]; then - echo "FATAL: Expected 4 arguments (actual $# $@)" >&2 - exit 1 - fi - - local remote_path="$(_iorapd_path_to_data_file "$package" "$activity" "compiled_trace.pb")" - - # See 'read_ahead.cc' LOG(INFO). - local pattern="Description = $remote_path" - logcat_wait_for_pattern "$timeout" "$timestamp" "$pattern" -} diff --git a/startop/scripts/iorap/compile_handcrafted_file.py b/startop/scripts/iorap/compile_handcrafted_file.py deleted file mode 100755 index 6dbbeaf91571..000000000000 --- a/startop/scripts/iorap/compile_handcrafted_file.py +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. - -import argparse -import asyncio -import csv -import itertools -import os -import re -import struct -import sys -import tempfile -import time -import zipfile -from typing import Any, Callable, Dict, Generic, Iterable, List, NamedTuple, TextIO, Tuple, TypeVar, Optional, Union - -# Include generated protos. -dir_name = os.path.dirname(os.path.realpath(__file__)) -sys.path.append(dir_name + "/generated") - -from TraceFile_pb2 import * - - -def parse_options(argv: List[str] = None): - """Parse command line arguments and return an argparse Namespace object.""" - parser = argparse.ArgumentParser(description="Compile a TraceFile.proto from a manual text file.") - # argparse considers args starting with - and -- optional in --help, even though required=True. - # by using a named argument group --help will clearly say that it's required instead of optional. - required_named = parser.add_argument_group('required named arguments') - - # optional arguments - # use a group here to get the required arguments to appear 'above' the optional arguments in help. - optional_named = parser.add_argument_group('optional named arguments') - optional_named.add_argument('-opb', '--output-proto-binary', dest='output_proto_binary', action='store', help='Write binary proto output to file.') - optional_named.add_argument('-pm', '--pinlist-meta', dest='pinlist_meta', action='store', help='Path to pinlist.meta (default=none) binary file.') - optional_named.add_argument('-pmp', '--pinlist-meta-parent', dest='pinlist_meta_parent', action='store', help='Device path that the pinlist.meta applies to (e.g. /data/.../somefile.apk)') - optional_named.add_argument('-i', '--input', dest='input', action='store', help='Input text file (default stdin).') - optional_named.add_argument('-zp', '--zip_path', dest='zip_path', action='append', help='Directory containing zip files.') - optional_named.add_argument('-d', '--debug', dest='debug', action='store_true', help='Add extra debugging output') - optional_named.add_argument('-ot', '--output-text', dest='output_text', action='store', help='Output text file (default stdout).') - - return parser.parse_args(argv) - -# TODO: refactor this with a common library file with analyze_metrics.py -def _debug_print(*args, **kwargs): - """Print the args to sys.stderr if the --debug/-d flag was passed in.""" - if _debug: - print(*args, **kwargs, file=sys.stderr) - -class BadInputError(Exception): - pass - -InputRecord = NamedTuple('InputRecord', [('filepath', str), ('offset', int), ('length', int), ('remark', str)]) - -def find_zip_in_paths(original_name, zip_paths): - # /foo/bar/bax.zip -> bax.zip - file_basename = os.path.split(original_name)[1] - - # the file must be located in one of the --zip-path arguments - matched = None - for zip_path in zip_paths: - for dir_entry in os.listdir(zip_path): - if dir_entry == file_basename: - matched = os.path.join(zip_path, dir_entry) - break - if matched: - break - - if not matched: - raise ValueError("%s could not be found in any of the --zip_path specified." %(file_basename)) - - _debug_print("found zip file ", file_basename, " in ", matched) - - if not zipfile.is_zipfile(matched): - raise ValueError("%s is not a zip file" %(matched)) - - return matched - -def handle_zip_entry(input_record, zip_paths): - - res = re.match("([^!]+)[!](.*)", input_record.filepath) - - if not res: - return input_record - - # 'foo!bar' - in_filepath = res[1] # -> 'foo' - in_zip_entry = res[2] # -> 'bar' - - matched = find_zip_in_paths(in_filepath, zip_paths) - - zip = zipfile.ZipFile(matched) - - try: - zip_info = zip.getinfo(in_zip_entry) - except KeyError: - raise ValueError("%s is not an item in the zip file %s" %(in_zip_entry, matched)) - - # TODO: do we also need to add header size to this? - in_offset = zip_info.header_offset - - # TODO: if a range is specified, use that instead. - in_length = zip_info.compress_size - - return InputRecord(in_filepath, in_offset, in_length, 'zip entry (%s)' %(in_zip_entry)) - -def parse_input_file(input: Iterable[str], zip_paths: List[str]) -> Iterable[InputRecord]: - for line in input: - line = line.strip() - - _debug_print("Line = ", line) - if not line: - _debug_print(" skip empty line", line) - continue - elif line[0] == "#": - _debug_print(" skip commented line", line) - continue - - res = re.match("([^\s]+)\s+(\d+)\s+(\d+)", line) - if not res: - raise BadInputError("Expected input of form: <str:filepath> <int:offset> <int:length>") - - in_filepath = res[1] - in_offset = int(res[2]) - in_length = int(res[3]) - - yield handle_zip_entry(InputRecord(in_filepath, in_offset, in_length, 'regular file'), zip_paths) - -# format: -# (<big_endian(i32):file_offset> <big_endian(i32):range_length>)+ -PIN_META_FORMAT = ">ii" -PIN_META_READ_SIZE = struct.calcsize(PIN_META_FORMAT) - -def parse_pin_meta(pin_meta_file, pinlist_meta_parent, zip_paths): - if not pin_meta_file: - return () - - global PIN_META_FORMAT - global PIN_META_READ_SIZE - - # '/data/app/com.google.android.GoogleCamera-aNQhzSznf4h_bvJ_MRbweQ==/base.apk' - # -> 'com.google.android.GoogleCamera' - package_name_match = re.match('/.*/(.*)-.*=/base.apk', pinlist_meta_parent) - - if not package_name_match: - raise ValueError("%s did not contain the <packagename>.apk" %(pinlist_meta_parent)) - - package_name = package_name_match[1] - # "com.google.android.GoogleCamera" -> "GoogleCamera.apk" - apk_name = package_name.split(".")[-1] + ".apk" - - path_to_zip_on_host = find_zip_in_paths(apk_name, zip_paths) - apk_file_size = os.path.getsize(path_to_zip_on_host) - _debug_print("APK path '%s' file size '%d'" %(path_to_zip_on_host, apk_file_size)) - - while True: - data = pin_meta_file.read(PIN_META_READ_SIZE) - - if not data: - break - - (pin_offset, pin_length) = struct.unpack(PIN_META_FORMAT, data) # (offset, length) - - remark = 'regular file (pinlist.meta)' - - remaining_size = apk_file_size - pin_offset - if remaining_size < 0: - print("WARNING: Clamp entry (%d, %d), offset too large (max file size = %d)" %(pin_offset, pin_length, apk_file_size)) - - pin_length = pin_length + remaining_size - pin_offset = pin_offset + remaining_size - - if pin_offset < 0: - pin_offset = 0 - - remark += '[clamped.offset]' - - pin_last_offset = pin_offset + pin_length - remaining_size = apk_file_size - pin_last_offset - - if remaining_size < 0: - print("WARNING: Clamp entry (%d, %d), length too large (max file size = %d)" %(pin_offset, pin_length, apk_file_size)) - pin_length = pin_length + remaining_size - - remark += '[clamped.length]' - - yield InputRecord(pinlist_meta_parent, pin_offset, pin_length, remark) - -def write_text_file_output(input_records: Iterable[InputRecord], output_text_file): - for rec in input_records: - output_text_file.write("%s %d %d #%s\n" %(rec.filepath, rec.offset, rec.length, rec.remark)) - -def build_trace_file(input_records: Iterable[InputRecord]) -> TraceFile: - trace_file = TraceFile() - trace_file_index = trace_file.index - - file_id_counter = 0 - file_id_map = {} # filename -> id - - stats_length_total = 0 - filename_stats = {} # filename -> total size - - for rec in input_records: - filename = rec.filepath - - file_id = file_id_map.get(filename) - if not file_id: - file_id = file_id_counter - file_id_map[filename] = file_id_counter - file_id_counter = file_id_counter + 1 - - file_index_entry = trace_file_index.entries.add() - file_index_entry.id = file_id - file_index_entry.file_name = filename - - # already in the file index, add the file entry. - file_entry = trace_file.list.entries.add() - file_entry.index_id = file_id - file_entry.file_length = rec.length - stats_length_total += file_entry.file_length - file_entry.file_offset = rec.offset - - filename_stats[filename] = filename_stats.get(filename, 0) + file_entry.file_length - - return trace_file - -def main(): - global _debug - - options= parse_options() - _debug = options.debug - _debug_print("parsed options: ", options) - - if not options.input: - input_file = sys.stdin - _debug_print("input = stdin") - else: - input_file = open(options.input) - _debug_print("input = (file)", options.input) - - if not options.output_proto_binary: - output_proto_file = None - else: - output_proto_file = open(options.output_proto_binary, 'wb') - _debug_print("output_proto_binary = ", output_proto_file) - - pinlist_meta_parent = options.pinlist_meta_parent - if options.pinlist_meta: - pin_meta_file = open(options.pinlist_meta, 'rb') - else: - pin_meta_file = None - - if (pinlist_meta_parent == None) != (pin_meta_file == None): - print("Options must be used together: --pinlist-meta and --pinlist-meta-path") - return 1 - - if not options.output_text: - output_text_file = sys.stdout - _debug_print("output = stdout") - else: - output_text_file = open(options.output_text, 'w') - _debug_print("output = (file)", options.output_text) - - zip_paths = options.zip_path or [] - - input_records = list(parse_pin_meta(pin_meta_file, pinlist_meta_parent, zip_paths)) - input_records = input_records + list(parse_input_file(input_file, zip_paths)) - - for p in input_records: - _debug_print(p) - - write_text_file_output(input_records, output_text_file) - output_text_file.close() - - out_proto = build_trace_file(input_records) - - if output_proto_file: - output_proto_file.write(out_proto.SerializeToString()) - output_proto_file.close() - - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/startop/scripts/iorap/compiler.py b/startop/scripts/iorap/compiler.py deleted file mode 100644 index 1426d34f325d..000000000000 --- a/startop/scripts/iorap/compiler.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. - -import importlib -import os -import sys -import tempfile -from enum import Enum -from typing import TextIO, List - -# local import -DIR = os.path.abspath(os.path.dirname(__file__)) -sys.path.append(os.path.dirname(DIR)) -import lib.print_utils as print_utils - -# Type of compiler. -class CompilerType(Enum): - HOST = 1 # iorap.cmd.compiler on host - DEVICE = 2 # adb shell iorap.cmd.compiler - RI = 3 # compiler.py - -def compile_perfetto_trace_ri( - argv: List[str], - compiler) -> TextIO: - print_utils.debug_print('Compile using RI compiler.') - compiler_trace_file = tempfile.NamedTemporaryFile() - argv.extend(['-o', compiler_trace_file.name]) - print_utils.debug_print(argv) - compiler.main([''] + argv) - return compiler_trace_file - -def compile_perfetto_trace_device(inodes_path: str, - package: str, - activity: str, - compiler) -> TextIO: - print_utils.debug_print('Compile using on-device compiler.') - compiler_trace_file = tempfile.NamedTemporaryFile() - compiler.main(inodes_path, package, activity, compiler_trace_file.name) - return compiler_trace_file - -def compile(compiler_type: CompilerType, - inodes_path: str, - ri_compiler_argv, - package: str, - activity: str) -> TextIO: - if compiler_type == CompilerType.RI: - compiler = importlib.import_module('iorap.compiler_ri') - compiler_trace_file = compile_perfetto_trace_ri(ri_compiler_argv, - compiler) - return compiler_trace_file - if compiler_type == CompilerType.DEVICE: - compiler = importlib.import_module('iorap.compiler_device') - compiler_trace_file = compile_perfetto_trace_device(inodes_path, - package, - activity, - compiler) - return compiler_trace_file - - # Should not arrive here. - raise ValueError('Unknown compiler type') diff --git a/startop/scripts/iorap/compiler_device.py b/startop/scripts/iorap/compiler_device.py deleted file mode 100644 index d941cd913fe1..000000000000 --- a/startop/scripts/iorap/compiler_device.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. - -import argparse -import os -import sys -from typing import List - -DIR = os.path.abspath(os.path.dirname(__file__)) -sys.path.append(os.path.dirname(DIR)) # framework/base/startop/script -import lib.print_utils as print_utils -import iorap.lib.iorapd_utils as iorapd_utils -from app_startup.lib.app_runner import AppRunner - -IORAP_COMMON_BASH_SCRIPT = os.path.join(DIR, 'common') - -def parse_options(argv: List[str] = None): - """Parses command line arguments and returns an argparse Namespace object.""" - parser = argparse.ArgumentParser(description="Compile perfetto trace file") - required_named = parser.add_argument_group('required named arguments') - - required_named.add_argument('-i', dest='inodes', metavar='FILE', - help='Read cached inode data from a file saved ' - 'earlier with pagecache.py -d') - required_named.add_argument('-p', dest='package', - help='Package of the app to be compiled') - - optional_named = parser.add_argument_group('optional named arguments') - optional_named.add_argument('-o', dest='output', - help='The compiled trace is stored into the output file') - optional_named.add_argument('-a', dest='activity', - help='Activity of the app to be compiled') - optional_named.add_argument('-d', dest='debug', action='store_true' - , help='Activity of the app to be compiled') - - return parser.parse_args(argv) - -def main(inodes, package, activity, output, **kwargs) -> int: - """Entries of the program.""" - if not activity: - activity = AppRunner.get_activity(package) - - passed = iorapd_utils.compile_perfetto_trace_on_device(package, activity, - inodes) - if passed and output: - iorapd_utils.get_iorapd_compiler_trace(package, activity, output) - - return 0 - -if __name__ == '__main__': - opts = parse_options() - if opts.debug: - print_utils.DEBUG = opts.debug - print_utils.debug_print(opts) - sys.exit(main(**(vars(opts)))) diff --git a/startop/scripts/iorap/compiler_ri.py b/startop/scripts/iorap/compiler_ri.py deleted file mode 100755 index 90fc8a8123c5..000000000000 --- a/startop/scripts/iorap/compiler_ri.py +++ /dev/null @@ -1,325 +0,0 @@ -#!/usr/bin/env python3 - -# -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# -# Dependencies: -# -# $> sudo apt-get install python3-pip -# $> pip3 install --user protobuf sqlalchemy sqlite3 -# - -import optparse -import os -import re -import sys -import tempfile -from pathlib import Path -from datetime import timedelta -from typing import Iterable, Optional, List - -DIR = os.path.abspath(os.path.dirname(__file__)) -sys.path.append(os.path.dirname(DIR)) -from iorap.generated.TraceFile_pb2 import * -from iorap.lib.inode2filename import Inode2Filename - -parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -sys.path.append(parent_dir_name) -from trace_analyzer.lib.trace2db import Trace2Db, MmFilemapAddToPageCache, \ - RawFtraceEntry -import lib.cmd_utils as cmd_utils - -_PAGE_SIZE = 4096 # adb shell getconf PAGESIZE ## size of a memory page in bytes. -ANDROID_BUILD_TOP = Path(parent_dir_name).parents[3] -TRACECONV_BIN = ANDROID_BUILD_TOP.joinpath( - 'external/perfetto/tools/traceconv') - -class PageRun: - """ - Intermediate representation for a run of one or more pages. - """ - def __init__(self, device_number: int, inode: int, offset: int, length: int): - self.device_number = device_number - self.inode = inode - self.offset = offset - self.length = length - - def __str__(self): - return "PageRun(device_number=%d, inode=%d, offset=%d, length=%d)" \ - %(self.device_number, self.inode, self.offset, self.length) - -def debug_print(msg): - #print(msg) - pass - -UNDER_LAUNCH = False - -def page_cache_entries_to_runs(page_cache_entries: Iterable[MmFilemapAddToPageCache]): - global _PAGE_SIZE - - runs = [ - PageRun(device_number=pg_entry.dev, inode=pg_entry.ino, offset=pg_entry.ofs, - length=_PAGE_SIZE) - for pg_entry in page_cache_entries - ] - - for r in runs: - debug_print(r) - - print("Stats: Page runs totaling byte length: %d" %(len(runs) * _PAGE_SIZE)) - - return runs - -def optimize_page_runs(page_runs): - new_entries = [] - last_entry = None - for pg_entry in page_runs: - if last_entry: - if pg_entry.device_number == last_entry.device_number and pg_entry.inode == last_entry.inode: - # we are dealing with a run for the same exact file as a previous run. - if pg_entry.offset == last_entry.offset + last_entry.length: - # trivially contiguous entries. merge them together. - last_entry.length += pg_entry.length - continue - # Default: Add the run without merging it to a previous run. - last_entry = pg_entry - new_entries.append(pg_entry) - return new_entries - -def is_filename_matching_filter(file_name, filters=[]): - """ - Blacklist-style regular expression filters. - - :return: True iff file_name has an RE match in one of the filters. - """ - for filt in filters: - res = re.search(filt, file_name) - if res: - return True - - return False - -def build_protobuf(page_runs, inode2filename, filters=[]): - trace_file = TraceFile() - trace_file_index = trace_file.index - - file_id_counter = 0 - file_id_map = {} # filename -> id - - stats_length_total = 0 - filename_stats = {} # filename -> total size - - skipped_inode_map = {} - filtered_entry_map = {} # filename -> count - - for pg_entry in page_runs: - fn = inode2filename.resolve(pg_entry.device_number, pg_entry.inode) - if not fn: - skipped_inode_map[pg_entry.inode] = skipped_inode_map.get(pg_entry.inode, 0) + 1 - continue - - filename = fn - - if filters and not is_filename_matching_filter(filename, filters): - filtered_entry_map[filename] = filtered_entry_map.get(filename, 0) + 1 - continue - - file_id = file_id_map.get(filename) - # file_id could 0, which satisfies "if file_id" and causes duplicate - # filename for file id 0. - if file_id is None: - file_id = file_id_counter - file_id_map[filename] = file_id_counter - file_id_counter = file_id_counter + 1 - - file_index_entry = trace_file_index.entries.add() - file_index_entry.id = file_id - file_index_entry.file_name = filename - - # already in the file index, add the file entry. - file_entry = trace_file.list.entries.add() - file_entry.index_id = file_id - file_entry.file_length = pg_entry.length - stats_length_total += file_entry.file_length - file_entry.file_offset = pg_entry.offset - - filename_stats[filename] = filename_stats.get(filename, 0) + file_entry.file_length - - for inode, count in skipped_inode_map.items(): - print("WARNING: Skip inode %s because it's not in inode map (%d entries)" %(inode, count)) - - print("Stats: Sum of lengths %d" %(stats_length_total)) - - if filters: - print("Filter: %d total files removed." %(len(filtered_entry_map))) - - for fn, count in filtered_entry_map.items(): - print("Filter: File '%s' removed '%d' entries." %(fn, count)) - - for filename, file_size in filename_stats.items(): - print("%s,%s" %(filename, file_size)) - - return trace_file - -def calc_trace_end_time(trace2db: Trace2Db, - trace_duration: Optional[timedelta]) -> float: - """ - Calculates the end time based on the trace duration. - The start time is the first receiving mm file map event. - The end time is the start time plus the trace duration. - All of them are in milliseconds. - """ - # If the duration is not set, assume all time is acceptable. - if trace_duration is None: - # float('inf') - return RawFtraceEntry.__table__.c.timestamp.type.python_type('inf') - - first_event = trace2db.session.query(MmFilemapAddToPageCache).join( - MmFilemapAddToPageCache.raw_ftrace_entry).order_by( - RawFtraceEntry.timestamp).first() - - # total_seconds() will return a float number. - return first_event.raw_ftrace_entry.timestamp + trace_duration.total_seconds() - -def query_add_to_page_cache(trace2db: Trace2Db, trace_duration: Optional[timedelta]): - end_time = calc_trace_end_time(trace2db, trace_duration) - # SELECT * FROM tbl ORDER BY id; - return trace2db.session.query(MmFilemapAddToPageCache).join( - MmFilemapAddToPageCache.raw_ftrace_entry).filter( - RawFtraceEntry.timestamp <= end_time).order_by( - MmFilemapAddToPageCache.id).all() - -def transform_perfetto_trace_to_systrace(path_to_perfetto_trace: str, - path_to_tmp_systrace: str) -> None: - """ Transforms the systrace file from perfetto trace. """ - cmd_utils.run_command_nofail([str(TRACECONV_BIN), - 'systrace', - path_to_perfetto_trace, - path_to_tmp_systrace]) - - -def run(sql_db_path:str, - trace_file:str, - trace_duration:Optional[timedelta], - output_file:str, - inode_table:str, - filter:List[str]) -> int: - trace2db = Trace2Db(sql_db_path) - # Speed optimization: Skip any entries that aren't mm_filemap_add_to_pagecache. - trace2db.set_raw_ftrace_entry_filter(\ - lambda entry: entry['function'] == 'mm_filemap_add_to_page_cache') - # TODO: parse multiple trace files here. - parse_count = trace2db.parse_file_into_db(trace_file) - - mm_filemap_add_to_page_cache_rows = query_add_to_page_cache(trace2db, - trace_duration) - print("DONE. Parsed %d entries into sql db." %(len(mm_filemap_add_to_page_cache_rows))) - - page_runs = page_cache_entries_to_runs(mm_filemap_add_to_page_cache_rows) - print("DONE. Converted %d entries" %(len(page_runs))) - - # TODO: flags to select optimizations. - optimized_page_runs = optimize_page_runs(page_runs) - print("DONE. Optimized down to %d entries" %(len(optimized_page_runs))) - - print("Build protobuf...") - trace_file = build_protobuf(optimized_page_runs, inode_table, filter) - - print("Write protobuf to file...") - output_file = open(output_file, 'wb') - output_file.write(trace_file.SerializeToString()) - output_file.close() - - print("DONE") - - # TODO: Silent running mode [no output except on error] for build runs. - - return 0 - -def main(argv): - parser = optparse.OptionParser(usage="Usage: %prog [options]", description="Compile systrace file into TraceFile.pb") - parser.add_option('-i', dest='inode_data_file', metavar='FILE', - help='Read cached inode data from a file saved earlier with pagecache.py -d') - parser.add_option('-t', dest='trace_file', metavar='FILE', - help='Path to systrace file (trace.html) that will be parsed') - parser.add_option('--perfetto-trace', dest='perfetto_trace_file', - metavar='FILE', - help='Path to perfetto trace that will be parsed') - - parser.add_option('--db', dest='sql_db', metavar='FILE', - help='Path to intermediate sqlite3 database [default: in-memory].') - - parser.add_option('-f', dest='filter', action="append", default=[], - help="Add file filter. All file entries not matching one of the filters are discarded.") - - parser.add_option('-l', dest='launch_lock', action="store_true", default=False, - help="Exclude all events not inside launch_lock") - - parser.add_option('-o', dest='output_file', metavar='FILE', - help='Output protobuf file') - - parser.add_option('--duration', dest='trace_duration', action="store", - type=int, help='The duration of trace in milliseconds.') - - options, categories = parser.parse_args(argv[1:]) - - # TODO: OptionParser should have some flags to make these mandatory. - if not options.inode_data_file: - parser.error("-i is required") - if not options.trace_file and not options.perfetto_trace_file: - parser.error("one of -t or --perfetto-trace is required") - if options.trace_file and options.perfetto_trace_file: - parser.error("please enter either -t or --perfetto-trace, not both") - if not options.output_file: - parser.error("-o is required") - - if options.launch_lock: - print("INFO: Launch lock flag (-l) enabled; filtering all events not inside launch_lock.") - - inode_table = Inode2Filename.new_from_filename(options.inode_data_file) - - sql_db_path = ":memory:" - if options.sql_db: - sql_db_path = options.sql_db - - trace_duration = timedelta(milliseconds=options.trace_duration) if \ - options.trace_duration is not None else None - - # if the input is systrace - if options.trace_file: - return run(sql_db_path, - options.trace_file, - trace_duration, - options.output_file, - inode_table, - options.filter) - - # if the input is perfetto trace - # TODO python 3.7 switch to using nullcontext - with tempfile.NamedTemporaryFile() as trace_file: - transform_perfetto_trace_to_systrace(options.perfetto_trace_file, - trace_file.name) - return run(sql_db_path, - trace_file.name, - trace_duration, - options.output_file, - inode_table, - options.filter) - -if __name__ == '__main__': - print(sys.argv) - sys.exit(main(sys.argv)) diff --git a/startop/scripts/iorap/compiler_test.py b/startop/scripts/iorap/compiler_test.py deleted file mode 100644 index b8de70147565..000000000000 --- a/startop/scripts/iorap/compiler_test.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. -# - -""" -Unit tests for the compiler.py script. - -Install: - $> sudo apt-get install python3-pytest ## OR - $> pip install -U pytest -See also https://docs.pytest.org/en/latest/getting-started.html - -Usage: - $> pytest compiler_test.py - -See also https://docs.pytest.org/en/latest/usage.html -""" -import os - -import compiler_ri as compiler - -DIR = os.path.abspath(os.path.dirname(__file__)) -TEXTCACHE = os.path.join(DIR, 'test_fixtures/compiler/common_textcache') -SYSTRACE = os.path.join(DIR, 'test_fixtures/compiler/common_systrace') -ARGV = [os.path.join(DIR, 'compiler.py'), '-i', TEXTCACHE, '-t', SYSTRACE] -PERFETTO_TRACE = os.path.join(DIR, - 'test_fixtures/compiler/common_perfetto_trace.pb') - -def assert_compile_result(output, expected, *extra_argv): - argv = ARGV + ['-o', output] + [args for args in extra_argv] - - compiler.main(argv) - - with open(output, 'rb') as f1, open(expected, 'rb') as f2: - assert f1.read() == f2.read() - -### Unit tests - testing compiler code directly -def test_transform_perfetto_trace_to_systrace(tmpdir): - expected = os.path.join(DIR, - 'test_fixtures/compiler/test_result_systrace') - output = tmpdir.mkdir('compiler').join('tmp_systrace') - - compiler.transform_perfetto_trace_to_systrace(PERFETTO_TRACE, str(output)) - - with open(output, 'rb') as f1, open(expected, 'rb') as f2: - assert f1.read() == f2.read() - -### Functional tests - calls 'compiler.py --args...' -def test_compiler_main(tmpdir): - output = tmpdir.mkdir('compiler').join('output') - - # No duration - expected = os.path.join(DIR, - 'test_fixtures/compiler/test_result_without_duration.TraceFile.pb') - assert_compile_result(output, expected) - - # 10ms duration - expected = os.path.join(DIR, - 'test_fixtures/compiler/test_result_with_duration.TraceFile.pb') - assert_compile_result(output, expected, '--duration', '10000') - - # 30ms duration - expected = os.path.join(DIR, - 'test_fixtures/compiler/test_result_without_duration.TraceFile.pb') - assert_compile_result(output, expected, '--duration', '30000') diff --git a/startop/scripts/iorap/dump_compiled_pb b/startop/scripts/iorap/dump_compiled_pb deleted file mode 100755 index ad26a7d72c53..000000000000 --- a/startop/scripts/iorap/dump_compiled_pb +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -# -# 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. - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -# -# Dumps an iorap compiler protobuf from iorap.cmd.compiler into text -# with gqui. -# - -if [[ "$#" -lt 1 ]]; then - echo "Usage: $0 <compiler_trace_file.pb> [...args]" >&2 - exit 1 -fi - -path_to_proto="$DIR/../../../../../system/iorap/src/serialize/TraceFile.proto" - -filename="$1" -shift -if ! [[ -f $filename ]]; then - echo "Error: $filename does not exist." >&2 - exit 1 -fi - -gqui "rawproto:$filename" proto "$path_to_proto":iorap.serialize.proto.TraceFile "$@" diff --git a/startop/scripts/iorap/dump_trace_pb b/startop/scripts/iorap/dump_trace_pb deleted file mode 100755 index bcec4a524994..000000000000 --- a/startop/scripts/iorap/dump_trace_pb +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -# -# 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. - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -# -# Dumps a perfetto protobuf collected by iorapd (from perfetto) into text -# with gqui. -# - -if [[ "$#" -lt 1 ]]; then - echo "Usage: $0 <perfetto_trace.pb> [...args]" >&2 - exit 1 -fi - -path_to_perfetto_proto="$DIR/../../../../../external/perfetto/protos/perfetto/trace/perfetto_trace.proto" - -filename="$1" -shift -if ! [[ -f $filename ]]; then - echo "Error: $filename does not exist." >&2 - exit 1 -fi - -gqui "rawproto:$filename" proto "$path_to_perfetto_proto":perfetto.protos.Trace "$@" diff --git a/startop/scripts/iorap/generated/TraceFile_pb2.py b/startop/scripts/iorap/generated/TraceFile_pb2.py deleted file mode 100644 index f005bed427ca..000000000000 --- a/startop/scripts/iorap/generated/TraceFile_pb2.py +++ /dev/null @@ -1,259 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: TraceFile.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='TraceFile.proto', - package='iorap.serialize.proto', - syntax='proto2', - serialized_pb=_b('\n\x0fTraceFile.proto\x12\x15iorap.serialize.proto\"u\n\tTraceFile\x12\x34\n\x05index\x18\x01 \x02(\x0b\x32%.iorap.serialize.proto.TraceFileIndex\x12\x32\n\x04list\x18\x02 \x02(\x0b\x32$.iorap.serialize.proto.TraceFileList\"M\n\x0eTraceFileIndex\x12;\n\x07\x65ntries\x18\x01 \x03(\x0b\x32*.iorap.serialize.proto.TraceFileIndexEntry\"4\n\x13TraceFileIndexEntry\x12\n\n\x02id\x18\x01 \x02(\x03\x12\x11\n\tfile_name\x18\x02 \x02(\t\"G\n\rTraceFileList\x12\x36\n\x07\x65ntries\x18\x01 \x03(\x0b\x32%.iorap.serialize.proto.TraceFileEntry\"L\n\x0eTraceFileEntry\x12\x10\n\x08index_id\x18\x01 \x02(\x03\x12\x13\n\x0b\x66ile_offset\x18\x02 \x02(\x03\x12\x13\n\x0b\x66ile_length\x18\x03 \x02(\x03\x42\x1c\n\x18\x63om.google.android.iorapH\x03') -) -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - - - - -_TRACEFILE = _descriptor.Descriptor( - name='TraceFile', - full_name='iorap.serialize.proto.TraceFile', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='index', full_name='iorap.serialize.proto.TraceFile.index', index=0, - number=1, type=11, cpp_type=10, label=2, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='list', full_name='iorap.serialize.proto.TraceFile.list', index=1, - number=2, type=11, cpp_type=10, label=2, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=42, - serialized_end=159, -) - - -_TRACEFILEINDEX = _descriptor.Descriptor( - name='TraceFileIndex', - full_name='iorap.serialize.proto.TraceFileIndex', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='entries', full_name='iorap.serialize.proto.TraceFileIndex.entries', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=161, - serialized_end=238, -) - - -_TRACEFILEINDEXENTRY = _descriptor.Descriptor( - name='TraceFileIndexEntry', - full_name='iorap.serialize.proto.TraceFileIndexEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='iorap.serialize.proto.TraceFileIndexEntry.id', index=0, - number=1, type=3, cpp_type=2, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='file_name', full_name='iorap.serialize.proto.TraceFileIndexEntry.file_name', index=1, - number=2, type=9, cpp_type=9, label=2, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=240, - serialized_end=292, -) - - -_TRACEFILELIST = _descriptor.Descriptor( - name='TraceFileList', - full_name='iorap.serialize.proto.TraceFileList', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='entries', full_name='iorap.serialize.proto.TraceFileList.entries', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=294, - serialized_end=365, -) - - -_TRACEFILEENTRY = _descriptor.Descriptor( - name='TraceFileEntry', - full_name='iorap.serialize.proto.TraceFileEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='index_id', full_name='iorap.serialize.proto.TraceFileEntry.index_id', index=0, - number=1, type=3, cpp_type=2, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='file_offset', full_name='iorap.serialize.proto.TraceFileEntry.file_offset', index=1, - number=2, type=3, cpp_type=2, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='file_length', full_name='iorap.serialize.proto.TraceFileEntry.file_length', index=2, - number=3, type=3, cpp_type=2, label=2, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto2', - extension_ranges=[], - oneofs=[ - ], - serialized_start=367, - serialized_end=443, -) - -_TRACEFILE.fields_by_name['index'].message_type = _TRACEFILEINDEX -_TRACEFILE.fields_by_name['list'].message_type = _TRACEFILELIST -_TRACEFILEINDEX.fields_by_name['entries'].message_type = _TRACEFILEINDEXENTRY -_TRACEFILELIST.fields_by_name['entries'].message_type = _TRACEFILEENTRY -DESCRIPTOR.message_types_by_name['TraceFile'] = _TRACEFILE -DESCRIPTOR.message_types_by_name['TraceFileIndex'] = _TRACEFILEINDEX -DESCRIPTOR.message_types_by_name['TraceFileIndexEntry'] = _TRACEFILEINDEXENTRY -DESCRIPTOR.message_types_by_name['TraceFileList'] = _TRACEFILELIST -DESCRIPTOR.message_types_by_name['TraceFileEntry'] = _TRACEFILEENTRY - -TraceFile = _reflection.GeneratedProtocolMessageType('TraceFile', (_message.Message,), dict( - DESCRIPTOR = _TRACEFILE, - __module__ = 'TraceFile_pb2' - # @@protoc_insertion_point(class_scope:iorap.serialize.proto.TraceFile) - )) -_sym_db.RegisterMessage(TraceFile) - -TraceFileIndex = _reflection.GeneratedProtocolMessageType('TraceFileIndex', (_message.Message,), dict( - DESCRIPTOR = _TRACEFILEINDEX, - __module__ = 'TraceFile_pb2' - # @@protoc_insertion_point(class_scope:iorap.serialize.proto.TraceFileIndex) - )) -_sym_db.RegisterMessage(TraceFileIndex) - -TraceFileIndexEntry = _reflection.GeneratedProtocolMessageType('TraceFileIndexEntry', (_message.Message,), dict( - DESCRIPTOR = _TRACEFILEINDEXENTRY, - __module__ = 'TraceFile_pb2' - # @@protoc_insertion_point(class_scope:iorap.serialize.proto.TraceFileIndexEntry) - )) -_sym_db.RegisterMessage(TraceFileIndexEntry) - -TraceFileList = _reflection.GeneratedProtocolMessageType('TraceFileList', (_message.Message,), dict( - DESCRIPTOR = _TRACEFILELIST, - __module__ = 'TraceFile_pb2' - # @@protoc_insertion_point(class_scope:iorap.serialize.proto.TraceFileList) - )) -_sym_db.RegisterMessage(TraceFileList) - -TraceFileEntry = _reflection.GeneratedProtocolMessageType('TraceFileEntry', (_message.Message,), dict( - DESCRIPTOR = _TRACEFILEENTRY, - __module__ = 'TraceFile_pb2' - # @@protoc_insertion_point(class_scope:iorap.serialize.proto.TraceFileEntry) - )) -_sym_db.RegisterMessage(TraceFileEntry) - - -DESCRIPTOR.has_options = True -DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\030com.google.android.iorapH\003')) -# @@protoc_insertion_point(module_scope) diff --git a/startop/scripts/iorap/generated/codegen_protos b/startop/scripts/iorap/generated/codegen_protos deleted file mode 100755 index 5688711ec25d..000000000000 --- a/startop/scripts/iorap/generated/codegen_protos +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# -# 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. - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -APROTOC="$(which aprotoc)" - -IORAP_SERIALIZE_DIR="${DIR}/../../../../../../system/iorap/src/serialize" -IORAP_PROTOS=($IORAP_SERIALIZE_DIR/*.proto) - -if [[ $? -ne 0 ]]; then - echo "Fatal: Missing aprotoc. Set APROTOC=... or lunch build/envsetup.sh?" >&2 - exit 1 -fi - -if ! [[ -d $IORAP_SERIALIZE_DIR ]]; then - echo "Fatal: Directory '$IORAP_SERIALIZE_DIR' does not exist." >&2 - exit 1 -fi - -# codegen the .py files into the same directory as this script. -echo "$APROTOC" --proto_path="$IORAP_SERIALIZE_DIR" --python_out="$DIR" "${IORAP_PROTOS[@]}" -"$APROTOC" --proto_path="$IORAP_SERIALIZE_DIR" --python_out="$DIR" "${IORAP_PROTOS[@]}" diff --git a/startop/scripts/iorap/lib/inode2filename.py b/startop/scripts/iorap/lib/inode2filename.py deleted file mode 100644 index 2e713936319a..000000000000 --- a/startop/scripts/iorap/lib/inode2filename.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 - -# -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from typing import Any, Callable, Dict, Generic, Iterable, List, NamedTuple, TextIO, Tuple, TypeVar, Optional, Union, TextIO - -import re - -class Inode2Filename: - """ - Parses a text file of the format - "uint(dev_t) uint(ino_t) int(file_size) string(filepath)\\n"* - - Lines not matching this format are ignored. - """ - - def __init__(self, inode_data_file: TextIO): - """ - Create an Inode2Filename that reads cached inode from a file saved earlier - (e.g. with pagecache.py -d or with inode2filename --format=textcache) - - :param inode_data_file: a file object (e.g. created with open or StringIO). - - Lifetime: inode_data_file is only used during the construction of the object. - """ - self._inode_table = Inode2Filename.build_inode_lookup_table(inode_data_file) - - @classmethod - def new_from_filename(cls, textcache_filename: str) -> 'Inode2Filename': - """ - Create an Inode2Filename that reads cached inode from a file saved earlier - (e.g. with pagecache.py -d or with inode2filename --format=textcache) - - :param textcache_filename: path to textcache - """ - with open(textcache_filename) as inode_data_file: - return cls(inode_data_file) - - @staticmethod - def build_inode_lookup_table(inode_data_file: TextIO) -> Dict[Tuple[int, int], Tuple[str, str]]: - """ - :return: map { (device_int, inode_int) -> (filename_str, size_str) } - """ - inode2filename = {} - for line in inode_data_file: - # stat -c "%d %i %s %n - # device number, inode number, total size in bytes, file name - result = re.match('([0-9]+)d? ([0-9]+) -?([0-9]+) (.*)', line) - if result: - inode2filename[(int(result.group(1)), int(result.group(2)))] = \ - (result.group(4), result.group(3)) - - return inode2filename - - def resolve(self, dev_t: int, ino_t: int) -> Optional[str]: - """ - Return a filename (str) from a (dev_t, ino_t) inode pair. - - Returns None if the lookup fails. - """ - maybe_result = self._inode_table.get((dev_t, ino_t)) - - if not maybe_result: - return None - - return maybe_result[0] # filename str - - def __len__(self) -> int: - """ - :return: the number of inode entries parsed from the file. - """ - return len(self._inode_table) - - def __repr__(self) -> str: - """ - :return: string representation for debugging/test failures. - """ - return "Inode2Filename%s" %(repr(self._inode_table)) - - # end of class. diff --git a/startop/scripts/iorap/lib/inode2filename_test.py b/startop/scripts/iorap/lib/inode2filename_test.py deleted file mode 100755 index 1224c61da641..000000000000 --- a/startop/scripts/iorap/lib/inode2filename_test.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. -# - -""" -Unit tests for inode2filename module. - -Install: - $> sudo apt-get install python3-pytest ## OR - $> pip install -U pytest -See also https://docs.pytest.org/en/latest/getting-started.html - -Usage: - $> ./inode2filename_test.py - $> pytest inode2filename_test.py - $> python -m pytest inode2filename_test.py - -See also https://docs.pytest.org/en/latest/usage.html -""" - -# global imports -from contextlib import contextmanager -import io -import shlex -import sys -import typing - -# pip imports -import pytest - -# local imports -from inode2filename import * - -def create_inode2filename(*contents): - buf = io.StringIO() - - for c in contents: - buf.write(c) - buf.write("\n") - - buf.seek(0) - - i2f = Inode2Filename(buf) - - buf.close() - - return i2f - -def test_inode2filename(): - a = create_inode2filename("") - assert len(a) == 0 - assert a.resolve(1, 2) == None - - a = create_inode2filename("1 2 3 foo.bar") - assert len(a) == 1 - assert a.resolve(1, 2) == "foo.bar" - assert a.resolve(4, 5) == None - - a = create_inode2filename("1 2 3 foo.bar", "4 5 6 bar.baz") - assert len(a) == 2 - assert a.resolve(1, 2) == "foo.bar" - assert a.resolve(4, 5) == "bar.baz" - - a = create_inode2filename("1567d 8910 -1 /a/b/c/", "4 5 6 bar.baz") - assert len(a) == 2 - assert a.resolve(1567, 8910) == "/a/b/c/" - assert a.resolve(4, 5) == "bar.baz" - -if __name__ == '__main__': - pytest.main() diff --git a/startop/scripts/iorap/lib/iorapd_utils.py b/startop/scripts/iorap/lib/iorapd_utils.py deleted file mode 100644 index f6f21fd70005..000000000000 --- a/startop/scripts/iorap/lib/iorapd_utils.py +++ /dev/null @@ -1,175 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. - -"""Helper util libraries for iorapd related operations.""" - -import os -import sys - -# up to two level -sys.path.append(os.path.join(os.path.abspath(__file__),'../..')) -import lib.cmd_utils as cmd_utils - -IORAPID_LIB_DIR = os.path.abspath(os.path.dirname(__file__)) -IORAPD_DATA_PATH = '/data/misc/iorapd' -IORAP_COMMON_BASH_SCRIPT = os.path.realpath(os.path.join(IORAPID_LIB_DIR, - '../common')) - -def _iorapd_path_to_data_file(package: str, activity: str, suffix: str) -> str: - """Gets conventional data filename. - - Returns: - The path of iorapd data file. - - """ - # Match logic of 'AppComponentName' in iorap::compiler C++ code. - return '{}/{}%2F{}.{}'.format(IORAPD_DATA_PATH, package, activity, suffix) - -def compile_perfetto_trace_on_device(package: str, activity: str, - inodes: str) -> bool: - """Compiles the perfetto trace using on-device compiler.""" - passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT, - 'iorapd_compiler_for_app_trace', - [package, activity, inodes]) - return passed - -def get_iorapd_compiler_trace(package: str, activity: str, dest: str) -> str: - """Gets compiler trace to dest file.""" - src = _iorapd_path_to_data_file(package, activity, 'compiled_trace.pb') - passed, _ = cmd_utils.run_shell_command('adb pull "{}" "{}"'.format(src, dest)) - if not passed: - return False - return True - -def iorapd_compiler_install_trace_file(package: str, activity: str, - input_file: str) -> bool: - """Installs a compiled trace file. - - Returns: - Whether the trace file is installed successful or not. - """ - # remote path calculations - compiled_path = _iorapd_path_to_data_file(package, activity, - 'compiled_trace.pb') - - if not os.path.exists(input_file): - print('Error: File {} does not exist'.format(input_file)) - return False - - passed, _ = cmd_utils.run_adb_shell_command( - 'mkdir -p "$(dirname "{}")"'.format(compiled_path)) - if not passed: - return False - - passed, _ = cmd_utils.run_shell_command('adb push "{}" "{}"'.format( - input_file, compiled_path)) - - return passed - -def wait_for_iorapd_finish(package: str, - activity: str, - timeout: int, - debug: bool, - logcat_timestamp: str)->bool: - """Waits for the finish of iorapd. - - Returns: - A bool indicates whether the iorapd is done successfully or not. - """ - # Set verbose for bash script based on debug flag. - if debug: - os.putenv('verbose', 'y') - - # Validate that readahead completes. - # If this fails for some reason, then this will also discard the timing of - # the run. - passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT, - 'iorapd_readahead_wait_until_finished', - [package, activity, logcat_timestamp, - str(timeout)]) - return passed - - -def enable_iorapd_readahead() -> bool: - """ - Disable readahead. Subsequent launches of an application will be sped up - by iorapd readahead prefetching. - - Returns: - A bool indicates whether the enabling is done successfully or not. - """ - passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT, - 'iorapd_readahead_enable', []) - return passed - -def disable_iorapd_readahead() -> bool: - """ - Disable readahead. Subsequent launches of an application will be not be sped - up by iorapd readahead prefetching. - - Returns: - A bool indicates whether the disabling is done successfully or not. - """ - passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT, - 'iorapd_readahead_disable', []) - return passed - -def enable_iorapd_perfetto() -> bool: - """ - Enable Perfetto. Subsequent launches of an application will record a perfetto - trace protobuf. - - Returns: - A bool indicates whether the enabling is done successfully or not. - """ - passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT, - 'iorapd_perfetto_enable', []) - return passed - -def disable_iorapd_perfetto() -> bool: - """ - Disable Perfetto. Subsequent launches of applications will no longer record - perfetto trace protobufs. - - Returns: - A bool indicates whether the disabling is done successfully or not. - """ - passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT, - 'iorapd_perfetto_disable', []) - return passed - -def start_iorapd() -> bool: - """ - Starts iorapd. - - Returns: - A bool indicates whether the starting is done successfully or not. - """ - passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT, - 'iorapd_start', []) - return passed - -def stop_iorapd() -> bool: - """ - Stops iorapd. - - Returns: - A bool indicates whether the stopping is done successfully or not. - """ - passed, _ = cmd_utils.run_shell_func(IORAP_COMMON_BASH_SCRIPT, - 'iorapd_stop', []) - return passed - diff --git a/startop/scripts/iorap/pull_textcache b/startop/scripts/iorap/pull_textcache deleted file mode 100755 index 05544263957d..000000000000 --- a/startop/scripts/iorap/pull_textcache +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# -# 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. - -if [[ $# -lt 1 ]]; then - echo "Usage: $0 <output-filename>" >&2 - exit 1 -fi - -# see compiler/main.cc for list of roots -adb shell iorap.inode2filename --output-format=textcache --output=/data/local/tmp/dumpcache --all --root=/system --root=/apex --root=/vendor --root=/data --root=/product --root=/metadata -adb pull /data/local/tmp/dumpcache "$1" diff --git a/startop/scripts/iorap/test_fixtures/compiler/common_perfetto_trace.pb b/startop/scripts/iorap/test_fixtures/compiler/common_perfetto_trace.pb Binary files differdeleted file mode 100644 index a47ad3d5d9ec..000000000000 --- a/startop/scripts/iorap/test_fixtures/compiler/common_perfetto_trace.pb +++ /dev/null diff --git a/startop/scripts/iorap/test_fixtures/compiler/common_systrace b/startop/scripts/iorap/test_fixtures/compiler/common_systrace deleted file mode 100644 index 4573738db5c6..000000000000 --- a/startop/scripts/iorap/test_fixtures/compiler/common_systrace +++ /dev/null @@ -1,5 +0,0 @@ -<...>-2965 (-----) [001] .... 10000.746629: mm_filemap_add_to_page_cache: dev 253:6 ino 1 page=00000000679ee1ec pfn=1299913 ofs=192512 -<...>-2965 (-----) [001] .... 10010.746664: mm_filemap_add_to_page_cache: dev 253:6 ino 2 page=0000000006cd2fb7 pfn=1296251 ofs=196608 -<...>-2965 (-----) [001] .... 10020.746677: mm_filemap_add_to_page_cache: dev 253:6 ino 3 page=00000000af82f3d6 pfn=1419330 ofs=200704 -<...>-2965 (-----) [001] .... 10030.746693: mm_filemap_add_to_page_cache: dev 253:6 ino 4 page=000000002840f054 pfn=1304928 ofs=204800 -<...>-2965 (-----) [001] .... 10040.746706: mm_filemap_add_to_page_cache: dev 253:6 ino 5 page=000000004a59da17 pfn=1288069 ofs=208896 diff --git a/startop/scripts/iorap/test_fixtures/compiler/common_textcache b/startop/scripts/iorap/test_fixtures/compiler/common_textcache deleted file mode 100644 index da03004ec6fb..000000000000 --- a/startop/scripts/iorap/test_fixtures/compiler/common_textcache +++ /dev/null @@ -1,2 +0,0 @@ -64774 1 -1 /system/test1 -64774 3 -1 /data/test2 diff --git a/startop/scripts/iorap/test_fixtures/compiler/test_result_systrace b/startop/scripts/iorap/test_fixtures/compiler/test_result_systrace deleted file mode 100644 index 59ff7537180b..000000000000 --- a/startop/scripts/iorap/test_fixtures/compiler/test_result_systrace +++ /dev/null @@ -1,748 +0,0 @@ -TRACE: -# tracer: nop -# -# entries-in-buffer/entries-written: 30624/30624 #P:4 -# -# _-----=> irqs-off -# / _----=> need-resched -# | / _---=> hardirq/softirq -# || / _--=> preempt-depth -# ||| / delay -# TASK-PID TGID CPU# |||| TIMESTAMP FUNCTION -# | | | | |||| | | - <unknown>-27388 (-----) [004] .... 1920260.530929: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1461937 ofs=9535488 - <unknown>-27388 (-----) [005] .... 1920260.532161: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1344589 ofs=9474048 - <unknown>-27388 (-----) [005] .... 1920260.532183: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1153671 ofs=9478144 - <unknown>-27388 (-----) [005] .... 1920260.532184: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1219563 ofs=9482240 - <unknown>-27388 (-----) [005] .... 1920260.532185: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1083162 ofs=9486336 - <unknown>-27388 (-----) [005] .... 1920260.532185: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1147318 ofs=9490432 - <unknown>-27388 (-----) [005] .... 1920260.532186: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1333594 ofs=9494528 - <unknown>-27388 (-----) [005] .... 1920260.532186: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1375715 ofs=9498624 - <unknown>-27388 (-----) [005] .... 1920260.532186: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1184831 ofs=9502720 - <unknown>-27388 (-----) [005] .... 1920260.532187: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1241653 ofs=9506816 - <unknown>-27388 (-----) [005] .... 1920260.532187: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1134975 ofs=9510912 - <unknown>-27388 (-----) [005] .... 1920260.532190: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1145772 ofs=9515008 - <unknown>-27388 (-----) [005] .... 1920260.532190: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1090457 ofs=9519104 - <unknown>-27388 (-----) [005] .... 1920260.532190: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1137942 ofs=9523200 - <unknown>-27388 (-----) [005] .... 1920260.532191: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1130123 ofs=9527296 - <unknown>-27388 (-----) [005] .... 1920260.532191: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1208783 ofs=9531392 - <unknown>-27388 (-----) [005] .... 1920260.532192: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1294989 ofs=9539584 - <unknown>-27388 (-----) [005] .... 1920260.532206: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1163979 ofs=9543680 - <unknown>-27388 (-----) [005] .... 1920260.532206: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1350628 ofs=9547776 - <unknown>-27388 (-----) [005] .... 1920260.532206: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1386717 ofs=9551872 - <unknown>-27388 (-----) [005] .... 1920260.532207: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1316148 ofs=9555968 - <unknown>-27388 (-----) [005] .... 1920260.532208: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1316419 ofs=9560064 - <unknown>-27388 (-----) [005] .... 1920260.532208: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1149076 ofs=9564160 - <unknown>-27388 (-----) [005] .... 1920260.532209: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1372772 ofs=9568256 - <unknown>-27388 (-----) [005] .... 1920260.532209: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1116389 ofs=9572352 - <unknown>-27388 (-----) [005] .... 1920260.532211: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1325458 ofs=9576448 - <unknown>-27388 (-----) [005] .... 1920260.532211: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1195423 ofs=9580544 - <unknown>-27388 (-----) [005] .... 1920260.532211: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1250964 ofs=9584640 - <unknown>-27388 (-----) [005] .... 1920260.532212: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1196027 ofs=9588736 - <unknown>-27388 (-----) [005] .... 1920260.532212: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1354059 ofs=9592832 - <unknown>-27388 (-----) [005] .... 1920260.532213: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1264649 ofs=9596928 - <unknown>-27388 (-----) [005] .... 1920260.532213: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1245285 ofs=9601024 - <unknown>-27388 (-----) [005] .... 1920260.535119: mm_filemap_add_to_page_cache: dev 0:64768 ino 588 page=0000000000000000 pfn=1411552 ofs=44244992 - <unknown>-27388 (-----) [005] .... 1920260.535129: mm_filemap_add_to_page_cache: dev 0:3 ino 0 page=0000000000000000 pfn=1483081 ofs=433524736 - <unknown>-27388 (-----) [004] .... 1920260.536144: mm_filemap_add_to_page_cache: dev 0:3 ino 0 page=0000000000000000 pfn=1276173 ofs=438185984 - <unknown>-27388 (-----) [004] .... 1920260.536462: mm_filemap_add_to_page_cache: dev 0:64768 ino 588 page=0000000000000000 pfn=1174575 ofs=44249088 - <unknown>-27388 (-----) [004] .... 1920260.536464: mm_filemap_add_to_page_cache: dev 0:64768 ino 588 page=0000000000000000 pfn=1126294 ofs=44253184 - <unknown>-27388 (-----) [004] .... 1920260.536464: mm_filemap_add_to_page_cache: dev 0:64768 ino 588 page=0000000000000000 pfn=1248232 ofs=44257280 - <unknown>-27388 (-----) [004] .... 1920260.537065: mm_filemap_add_to_page_cache: dev 0:64768 ino 588 page=0000000000000000 pfn=1332993 ofs=44240896 - <unknown>-27388 (-----) [006] .... 1920260.537646: mm_filemap_add_to_page_cache: dev 0:64768 ino 588 page=0000000000000000 pfn=1153343 ofs=44400640 - <unknown>-27388 (-----) [005] .... 1920260.538777: mm_filemap_add_to_page_cache: dev 0:64768 ino 588 page=0000000000000000 pfn=1358397 ofs=44474368 - <unknown>-12683 (-----) [006] .... 1920260.560094: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1426577 ofs=0 - <unknown>-12683 (-----) [006] .... 1920260.560105: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1117587 ofs=1171456 - <unknown>-12683 (-----) [006] .... 1920260.561199: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1099987 ofs=4096 - <unknown>-12683 (-----) [006] .... 1920260.561411: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1099910 ofs=16384 - <unknown>-12683 (-----) [006] .... 1920260.561598: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1099905 ofs=20480 - <unknown>-12683 (-----) [006] .... 1920260.561758: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1099883 ofs=32768 - <unknown>-12683 (-----) [006] .... 1920260.562088: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1099809 ofs=36864 - <unknown>-12683 (-----) [006] .... 1920260.562325: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1099803 ofs=98304 - <unknown>-12683 (-----) [006] .... 1920260.562516: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1099795 ofs=102400 - <unknown>-12683 (-----) [006] .... 1920260.563094: mm_filemap_add_to_page_cache: dev 0:64768 ino 5f3 page=0000000000000000 pfn=1107649 ofs=12288 - <unknown>-12683 (-----) [006] .... 1920260.563105: mm_filemap_add_to_page_cache: dev 0:64768 ino 5f3 page=0000000000000000 pfn=1269029 ofs=16384 - <unknown>-12683 (-----) [006] .... 1920260.563785: mm_filemap_add_to_page_cache: dev 0:64768 ino 4da page=0000000000000000 pfn=1451096 ofs=8192 - <unknown>-12683 (-----) [006] .... 1920260.563790: mm_filemap_add_to_page_cache: dev 0:64768 ino 4da page=0000000000000000 pfn=1301480 ofs=12288 - <unknown>-12683 (-----) [006] .... 1920260.563790: mm_filemap_add_to_page_cache: dev 0:64768 ino 4da page=0000000000000000 pfn=1314353 ofs=16384 - <unknown>-12683 (-----) [006] .... 1920260.563791: mm_filemap_add_to_page_cache: dev 0:64768 ino 4da page=0000000000000000 pfn=1216744 ofs=24576 - <unknown>-12683 (-----) [006] .... 1920260.564309: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1099787 ofs=49152 - <unknown>-12683 (-----) [006] .... 1920260.564514: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1099778 ofs=53248 - <unknown>-12683 (-----) [005] .... 1920260.564756: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1148849 ofs=114688 - <unknown>-12683 (-----) [005] .... 1920260.564973: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1164731 ofs=118784 - <unknown>-12683 (-----) [005] .... 1920260.565000: mm_filemap_add_to_page_cache: dev 0:2053 ino 1a page=0000000000000000 pfn=1170255 ofs=0 - <unknown>-12683 (-----) [005] .... 1920260.565003: mm_filemap_add_to_page_cache: dev 0:2053 ino 1a page=0000000000000000 pfn=1181043 ofs=4096 - <unknown>-12683 (-----) [005] .... 1920260.565004: mm_filemap_add_to_page_cache: dev 0:2053 ino 1a page=0000000000000000 pfn=1296004 ofs=8192 - <unknown>-12683 (-----) [005] .... 1920260.565004: mm_filemap_add_to_page_cache: dev 0:2053 ino 1a page=0000000000000000 pfn=1102004 ofs=12288 - <unknown>-12683 (-----) [005] .... 1920260.565626: mm_filemap_add_to_page_cache: dev 0:3 ino 0 page=0000000000000000 pfn=1351232 ofs=470597632 - <unknown>-12683 (-----) [005] .... 1920260.565982: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1391336 ofs=40210432 - <unknown>-12683 (-----) [005] .... 1920260.565985: mm_filemap_add_to_page_cache: dev 0:64771 ino 2 page=0000000000000000 pfn=1267536 ofs=12668928 - <unknown>-27388 (-----) [007] .... 1920260.566082: mm_filemap_add_to_page_cache: dev 0:64768 ino 588 page=0000000000000000 pfn=1256752 ofs=43921408 - <unknown>-12683 (-----) [005] .... 1920260.566516: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1110966 ofs=176226304 - <unknown>-12683 (-----) [005] .... 1920260.566519: mm_filemap_add_to_page_cache: dev 0:64771 ino 2 page=0000000000000000 pfn=1060586 ofs=12967936 - <unknown>-12683 (-----) [004] .... 1920260.567773: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1117234 ofs=421888 - <unknown>-12683 (-----) [005] .... 1920260.568604: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1210571 ofs=430080 - <unknown>-12683 (-----) [005] .... 1920260.568887: mm_filemap_add_to_page_cache: dev 0:64771 ino 69 page=0000000000000000 pfn=1055640 ofs=0 - <unknown>-12683 (-----) [005] .... 1920260.568908: mm_filemap_add_to_page_cache: dev 0:64771 ino 49 page=0000000000000000 pfn=1142694 ofs=0 - <unknown>-12683 (-----) [005] .... 1920260.568910: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1060788 ofs=299008 - <unknown>-12683 (-----) [005] .... 1920260.569418: mm_filemap_add_to_page_cache: dev 0:64771 ino 49 page=0000000000000000 pfn=1085046 ofs=4096 - <unknown>-12683 (-----) [005] .... 1920260.569640: mm_filemap_add_to_page_cache: dev 0:64771 ino 49 page=0000000000000000 pfn=1057135 ofs=8192 - <unknown>-12683 (-----) [005] .... 1920260.569833: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1058976 ofs=19406848 - <unknown>-12683 (-----) [005] .... 1920260.569835: mm_filemap_add_to_page_cache: dev 0:64771 ino 2 page=0000000000000000 pfn=1477947 ofs=10526720 - <unknown>-12683 (-----) [005] .... 1920260.572285: mm_filemap_add_to_page_cache: dev 0:64768 ino 61d page=0000000000000000 pfn=1237492 ofs=299008 - <unknown>-12683 (-----) [005] .... 1920260.572297: mm_filemap_add_to_page_cache: dev 0:64768 ino 61d page=0000000000000000 pfn=1264914 ofs=339968 - <unknown>-12683 (-----) [005] .... 1920260.572314: mm_filemap_add_to_page_cache: dev 0:64768 ino 61d page=0000000000000000 pfn=1434748 ofs=348160 - <unknown>-12683 (-----) [005] .... 1920260.572316: mm_filemap_add_to_page_cache: dev 0:64768 ino 61d page=0000000000000000 pfn=1372959 ofs=352256 - <unknown>-12683 (-----) [005] .... 1920260.572317: mm_filemap_add_to_page_cache: dev 0:64768 ino 61d page=0000000000000000 pfn=1258955 ofs=356352 - <unknown>-12683 (-----) [005] .... 1920260.572317: mm_filemap_add_to_page_cache: dev 0:64768 ino 61d page=0000000000000000 pfn=1113420 ofs=360448 - <unknown>-12683 (-----) [005] .... 1920260.572318: mm_filemap_add_to_page_cache: dev 0:64768 ino 61d page=0000000000000000 pfn=1137083 ofs=364544 - <unknown>-12683 (-----) [004] .... 1920260.575490: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1379679 ofs=65536 - <unknown>-12683 (-----) [006] .... 1920260.576194: mm_filemap_add_to_page_cache: dev 0:64771 ino 11e page=0000000000000000 pfn=1323898 ofs=69632 - <unknown>-12683 (-----) [006] .... 1920260.576248: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1323895 ofs=262623232 - <unknown>-12683 (-----) [006] .... 1920260.576251: mm_filemap_add_to_page_cache: dev 0:64771 ino 2 page=0000000000000000 pfn=1323861 ofs=13156352 - <unknown>-12683 (-----) [005] .... 1920260.576810: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1477585 ofs=262590464 - <unknown>-12683 (-----) [004] .... 1920260.577197: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1267617 ofs=25206784 - <unknown>-12683 (-----) [004] .... 1920260.577200: mm_filemap_add_to_page_cache: dev 0:64771 ino 2 page=0000000000000000 pfn=1267618 ofs=12636160 - <unknown>-12683 (-----) [005] .... 1920260.577725: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1056225 ofs=228618240 - <unknown>-12683 (-----) [005] .... 1920260.577727: mm_filemap_add_to_page_cache: dev 0:64771 ino 2 page=0000000000000000 pfn=1164942 ofs=13082624 - <unknown>-12683 (-----) [007] .... 1920260.578411: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1372616 ofs=0 - <unknown>-12683 (-----) [007] .... 1920260.578422: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1307468 ofs=4096 - <unknown>-12683 (-----) [007] .... 1920260.578428: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1120117 ofs=8192 - <unknown>-12683 (-----) [007] .... 1920260.578428: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1217989 ofs=12288 - <unknown>-12683 (-----) [007] .... 1920260.578650: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1475011 ofs=5419008 - <unknown>-12683 (-----) [007] .... 1920260.578653: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1066084 ofs=236453888 - <unknown>-12683 (-----) [007] .... 1920260.578654: mm_filemap_add_to_page_cache: dev 0:64771 ino 2 page=0000000000000000 pfn=1100271 ofs=13099008 - <unknown>-12683 (-----) [004] .... 1920260.579004: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1485156 ofs=5423104 - <unknown>-12683 (-----) [004] .... 1920260.579005: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1124212 ofs=5427200 - <unknown>-12683 (-----) [004] .... 1920260.579006: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1195377 ofs=5431296 - <unknown>-12683 (-----) [004] .... 1920260.579006: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1265888 ofs=5435392 - <unknown>-12683 (-----) [004] .... 1920260.579007: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1170194 ofs=5439488 - <unknown>-12683 (-----) [004] .... 1920260.579007: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1403742 ofs=5443584 - <unknown>-12683 (-----) [004] .... 1920260.579008: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1123826 ofs=5447680 - <unknown>-12683 (-----) [004] .... 1920260.579008: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1255034 ofs=5451776 - <unknown>-12683 (-----) [004] .... 1920260.579011: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1190447 ofs=5455872 - <unknown>-12683 (-----) [004] .... 1920260.579011: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1286864 ofs=5459968 - <unknown>-12683 (-----) [004] .... 1920260.579012: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1428535 ofs=5464064 - <unknown>-12683 (-----) [004] .... 1920260.579012: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1184092 ofs=5468160 - <unknown>-12683 (-----) [004] .... 1920260.579013: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1411906 ofs=5472256 - <unknown>-12683 (-----) [004] .... 1920260.579013: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1342349 ofs=5476352 - <unknown>-12683 (-----) [004] .... 1920260.579013: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1188185 ofs=5480448 - <unknown>-12683 (-----) [004] .... 1920260.579014: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1158702 ofs=5484544 - <unknown>-12683 (-----) [005] .... 1920260.579430: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1299421 ofs=5230592 - <unknown>-12683 (-----) [005] .... 1920260.579435: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1317097 ofs=5234688 - <unknown>-12683 (-----) [005] .... 1920260.579435: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1441714 ofs=5238784 - <unknown>-12683 (-----) [005] .... 1920260.579438: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1081974 ofs=5242880 - <unknown>-12683 (-----) [005] .... 1920260.579439: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1128684 ofs=5246976 - <unknown>-12683 (-----) [005] .... 1920260.579439: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1447381 ofs=5251072 - <unknown>-12683 (-----) [005] .... 1920260.579440: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1466410 ofs=5255168 - <unknown>-12683 (-----) [005] .... 1920260.579440: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1259909 ofs=5259264 - <unknown>-12683 (-----) [005] .... 1920260.579441: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1125784 ofs=5263360 - <unknown>-12683 (-----) [005] .... 1920260.579441: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1270592 ofs=5267456 - <unknown>-12683 (-----) [005] .... 1920260.579442: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1246070 ofs=5271552 - <unknown>-12683 (-----) [005] .... 1920260.579442: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1472544 ofs=5275648 - <unknown>-12683 (-----) [005] .... 1920260.579442: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1113357 ofs=5279744 - <unknown>-12683 (-----) [005] .... 1920260.579443: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1202021 ofs=5283840 - <unknown>-12683 (-----) [005] .... 1920260.579443: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1078639 ofs=5287936 - <unknown>-12683 (-----) [005] .... 1920260.579449: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1176171 ofs=5292032 - <unknown>-12683 (-----) [005] .... 1920260.579450: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1089516 ofs=5296128 - <unknown>-12683 (-----) [005] .... 1920260.579451: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1400065 ofs=5300224 - <unknown>-12683 (-----) [005] .... 1920260.579452: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1300489 ofs=5304320 - <unknown>-12683 (-----) [005] .... 1920260.579452: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1452081 ofs=5308416 - <unknown>-12683 (-----) [005] .... 1920260.579452: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1161862 ofs=5312512 - <unknown>-12683 (-----) [005] .... 1920260.579453: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1161871 ofs=5316608 - <unknown>-12683 (-----) [005] .... 1920260.579453: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1263798 ofs=5320704 - <unknown>-12683 (-----) [005] .... 1920260.579454: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1126887 ofs=5324800 - <unknown>-12683 (-----) [005] .... 1920260.579454: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1375498 ofs=5328896 - <unknown>-12683 (-----) [005] .... 1920260.579455: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1328067 ofs=5332992 - <unknown>-12683 (-----) [005] .... 1920260.579455: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1420691 ofs=5337088 - <unknown>-12683 (-----) [005] .... 1920260.579456: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1298707 ofs=5341184 - <unknown>-12683 (-----) [005] .... 1920260.579456: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1078670 ofs=5345280 - <unknown>-12683 (-----) [005] .... 1920260.579457: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1430498 ofs=5349376 - <unknown>-12683 (-----) [005] .... 1920260.579458: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1338720 ofs=5353472 - <unknown>-12683 (-----) [005] .... 1920260.579476: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1452611 ofs=5357568 - <unknown>-12683 (-----) [006] .... 1920260.580451: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1241967 ofs=0 - <unknown>-12683 (-----) [006] .... 1920260.580454: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1116541 ofs=4096 - <unknown>-12683 (-----) [006] .... 1920260.580461: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1145049 ofs=8192 - <unknown>-12683 (-----) [006] .... 1920260.580462: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1277255 ofs=12288 - <unknown>-12683 (-----) [006] .... 1920260.580462: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1098037 ofs=16384 - <unknown>-12683 (-----) [006] .... 1920260.580463: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1135986 ofs=20480 - <unknown>-12683 (-----) [006] .... 1920260.580464: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1154455 ofs=24576 - <unknown>-12683 (-----) [006] .... 1920260.580464: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1221822 ofs=28672 - <unknown>-12683 (-----) [006] .... 1920260.580465: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1078684 ofs=32768 - <unknown>-12683 (-----) [006] .... 1920260.580465: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1158876 ofs=36864 - <unknown>-12683 (-----) [006] .... 1920260.580465: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1289644 ofs=40960 - <unknown>-12683 (-----) [006] .... 1920260.580466: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1289386 ofs=45056 - <unknown>-12683 (-----) [006] .... 1920260.580466: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1131002 ofs=49152 - <unknown>-12683 (-----) [006] .... 1920260.580467: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1464335 ofs=53248 - <unknown>-12683 (-----) [006] .... 1920260.580468: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1135789 ofs=57344 - <unknown>-12683 (-----) [006] .... 1920260.580469: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1240897 ofs=61440 - <unknown>-12683 (-----) [006] .... 1920260.580469: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1241770 ofs=65536 - <unknown>-12683 (-----) [006] .... 1920260.580470: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1421959 ofs=69632 - <unknown>-12683 (-----) [006] .... 1920260.580470: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1230007 ofs=73728 - <unknown>-12683 (-----) [006] .... 1920260.580471: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1109271 ofs=77824 - <unknown>-12683 (-----) [006] .... 1920260.580471: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1159974 ofs=81920 - <unknown>-12683 (-----) [006] .... 1920260.580471: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1154528 ofs=86016 - <unknown>-12683 (-----) [006] .... 1920260.580472: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1315790 ofs=90112 - <unknown>-12683 (-----) [006] .... 1920260.580473: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1185583 ofs=94208 - <unknown>-12683 (-----) [006] .... 1920260.580473: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1253153 ofs=98304 - <unknown>-12683 (-----) [006] .... 1920260.580473: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103982 ofs=102400 - <unknown>-12683 (-----) [006] .... 1920260.580474: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1284589 ofs=106496 - <unknown>-12683 (-----) [006] .... 1920260.580474: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1169601 ofs=110592 - <unknown>-12683 (-----) [006] .... 1920260.580476: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1206248 ofs=114688 - <unknown>-12683 (-----) [006] .... 1920260.580476: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1261161 ofs=118784 - <unknown>-12683 (-----) [006] .... 1920260.580477: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1305841 ofs=122880 - <unknown>-12683 (-----) [006] .... 1920260.580477: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1468293 ofs=126976 - <unknown>-12683 (-----) [004] .... 1920260.580646: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1318816 ofs=16384 - <unknown>-12683 (-----) [004] .... 1920260.580649: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1472922 ofs=20480 - <unknown>-12683 (-----) [004] .... 1920260.580650: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1473229 ofs=24576 - <unknown>-12683 (-----) [004] .... 1920260.580650: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1524262 ofs=28672 - <unknown>-12683 (-----) [004] .... 1920260.580656: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1205714 ofs=32768 - <unknown>-12683 (-----) [004] .... 1920260.580657: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1310560 ofs=36864 - <unknown>-12683 (-----) [004] .... 1920260.580658: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1295070 ofs=40960 - <unknown>-12683 (-----) [004] .... 1920260.580659: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1404093 ofs=45056 - <unknown>-12683 (-----) [004] .... 1920260.580659: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1435814 ofs=49152 - <unknown>-12683 (-----) [004] .... 1920260.580660: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1435442 ofs=53248 - <unknown>-12683 (-----) [004] .... 1920260.580660: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1096077 ofs=57344 - <unknown>-12683 (-----) [004] .... 1920260.580661: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1483793 ofs=61440 - <unknown>-12683 (-----) [004] .... 1920260.580661: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1231298 ofs=65536 - <unknown>-12683 (-----) [004] .... 1920260.580661: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1215648 ofs=69632 - <unknown>-12683 (-----) [004] .... 1920260.580662: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1327326 ofs=73728 - <unknown>-12683 (-----) [004] .... 1920260.580662: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1108894 ofs=77824 - <unknown>-12683 (-----) [004] .... 1920260.580663: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1327545 ofs=81920 - <unknown>-12683 (-----) [004] .... 1920260.580663: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1328804 ofs=86016 - <unknown>-12683 (-----) [004] .... 1920260.580664: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1300171 ofs=90112 - <unknown>-12683 (-----) [004] .... 1920260.580664: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1353250 ofs=94208 - <unknown>-12683 (-----) [004] .... 1920260.580668: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1333681 ofs=98304 - <unknown>-12683 (-----) [004] .... 1920260.580668: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1144969 ofs=102400 - <unknown>-12683 (-----) [004] .... 1920260.580669: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1450962 ofs=106496 - <unknown>-12683 (-----) [004] .... 1920260.580669: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1255701 ofs=110592 - <unknown>-12683 (-----) [004] .... 1920260.580670: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1294782 ofs=114688 - <unknown>-12683 (-----) [004] .... 1920260.580670: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1226912 ofs=118784 - <unknown>-12683 (-----) [004] .... 1920260.580671: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1294579 ofs=122880 - <unknown>-12683 (-----) [004] .... 1920260.580671: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1246960 ofs=126976 - <unknown>-12683 (-----) [004] .... 1920260.580671: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1199086 ofs=131072 - <unknown>-12683 (-----) [004] .... 1920260.580672: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1449590 ofs=135168 - <unknown>-12683 (-----) [004] .... 1920260.580672: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1276363 ofs=139264 - <unknown>-12683 (-----) [004] .... 1920260.580675: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1389998 ofs=143360 - <unknown>-12683 (-----) [004] .... 1920260.580739: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1423031 ofs=1249280 - <unknown>-12683 (-----) [004] .... 1920260.580741: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1171032 ofs=1253376 - <unknown>-12683 (-----) [004] .... 1920260.580742: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1320946 ofs=1257472 - <unknown>-12683 (-----) [004] .... 1920260.580743: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1314696 ofs=1261568 - <unknown>-12683 (-----) [004] .... 1920260.580743: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1414864 ofs=1265664 - <unknown>-12683 (-----) [004] .... 1920260.580744: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1334933 ofs=1269760 - <unknown>-12683 (-----) [004] .... 1920260.580744: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1242845 ofs=1273856 - <unknown>-12683 (-----) [004] .... 1920260.580747: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1289488 ofs=1277952 - <unknown>-12683 (-----) [004] .... 1920260.580748: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1335445 ofs=1282048 - <unknown>-12683 (-----) [004] .... 1920260.580748: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1289663 ofs=1286144 - <unknown>-12683 (-----) [004] .... 1920260.580749: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1080462 ofs=1290240 - <unknown>-12683 (-----) [004] .... 1920260.580749: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1286303 ofs=1294336 - <unknown>-12683 (-----) [004] .... 1920260.580750: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1353531 ofs=1298432 - <unknown>-12683 (-----) [004] .... 1920260.580750: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1280701 ofs=1302528 - <unknown>-12683 (-----) [004] .... 1920260.580751: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1107730 ofs=1306624 - <unknown>-12683 (-----) [004] .... 1920260.580752: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1242729 ofs=1310720 - <unknown>-12683 (-----) [004] .... 1920260.580753: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1078336 ofs=1314816 - <unknown>-12683 (-----) [004] .... 1920260.580753: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1372425 ofs=1318912 - <unknown>-12683 (-----) [004] .... 1920260.580754: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1248813 ofs=1323008 - <unknown>-12683 (-----) [004] .... 1920260.580754: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1201155 ofs=1327104 - <unknown>-12683 (-----) [004] .... 1920260.580755: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1250103 ofs=1331200 - <unknown>-12683 (-----) [004] .... 1920260.580755: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1359710 ofs=1335296 - <unknown>-12683 (-----) [004] .... 1920260.580756: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1272462 ofs=1339392 - <unknown>-12683 (-----) [004] .... 1920260.580758: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1097035 ofs=1343488 - <unknown>-12683 (-----) [004] .... 1920260.580759: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1233124 ofs=1347584 - <unknown>-12683 (-----) [004] .... 1920260.580759: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1455812 ofs=1351680 - <unknown>-12683 (-----) [004] .... 1920260.580759: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1355689 ofs=1355776 - <unknown>-12683 (-----) [004] .... 1920260.580760: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1263593 ofs=1359872 - <unknown>-12683 (-----) [004] .... 1920260.580760: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1230789 ofs=1363968 - <unknown>-12683 (-----) [004] .... 1920260.580761: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1143766 ofs=1368064 - <unknown>-12683 (-----) [004] .... 1920260.580762: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1269666 ofs=1372160 - <unknown>-12683 (-----) [004] .... 1920260.580762: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1353022 ofs=1376256 - <unknown>-12683 (-----) [004] .... 1920260.581613: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1355509 ofs=258048 - <unknown>-12683 (-----) [004] .... 1920260.581615: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1178902 ofs=262144 - <unknown>-12683 (-----) [004] .... 1920260.581616: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1193649 ofs=266240 - <unknown>-12683 (-----) [004] .... 1920260.581618: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1225497 ofs=270336 - <unknown>-12683 (-----) [004] .... 1920260.581618: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1228259 ofs=274432 - <unknown>-12683 (-----) [004] .... 1920260.581635: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1309674 ofs=278528 - <unknown>-12683 (-----) [004] .... 1920260.581635: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1239390 ofs=282624 - <unknown>-12683 (-----) [004] .... 1920260.581636: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1468083 ofs=286720 - <unknown>-12683 (-----) [004] .... 1920260.581636: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1292751 ofs=290816 - <unknown>-12683 (-----) [004] .... 1920260.581637: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1318066 ofs=294912 - <unknown>-12683 (-----) [004] .... 1920260.581637: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1489314 ofs=299008 - <unknown>-12683 (-----) [004] .... 1920260.581637: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1169867 ofs=303104 - <unknown>-12683 (-----) [004] .... 1920260.581639: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1314256 ofs=307200 - <unknown>-12683 (-----) [004] .... 1920260.581639: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1310230 ofs=311296 - <unknown>-12683 (-----) [004] .... 1920260.581640: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1356180 ofs=315392 - <unknown>-12683 (-----) [004] .... 1920260.581640: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1419179 ofs=319488 - <unknown>-12683 (-----) [004] .... 1920260.581641: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1307265 ofs=323584 - <unknown>-12683 (-----) [004] .... 1920260.581641: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1218590 ofs=327680 - <unknown>-12683 (-----) [004] .... 1920260.581642: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1447586 ofs=331776 - <unknown>-12683 (-----) [004] .... 1920260.581642: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1209382 ofs=335872 - <unknown>-12683 (-----) [004] .... 1920260.581642: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1072148 ofs=339968 - <unknown>-12683 (-----) [004] .... 1920260.581645: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1227195 ofs=344064 - <unknown>-12683 (-----) [004] .... 1920260.581646: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1246369 ofs=348160 - <unknown>-12683 (-----) [004] .... 1920260.581646: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1193845 ofs=352256 - <unknown>-12683 (-----) [004] .... 1920260.581647: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1137553 ofs=356352 - <unknown>-12683 (-----) [004] .... 1920260.581647: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1475215 ofs=360448 - <unknown>-12683 (-----) [004] .... 1920260.581648: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1258935 ofs=364544 - <unknown>-12683 (-----) [004] .... 1920260.581649: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1448788 ofs=368640 - <unknown>-12683 (-----) [004] .... 1920260.581649: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1447611 ofs=372736 - <unknown>-12683 (-----) [004] .... 1920260.581650: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1290842 ofs=376832 - <unknown>-12683 (-----) [004] .... 1920260.581650: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1447826 ofs=380928 - <unknown>-12683 (-----) [004] .... 1920260.581650: mm_filemap_add_to_page_cache: dev 0:64771 ino da07 page=0000000000000000 pfn=1181016 ofs=385024 - <unknown>-12683 (-----) [005] .... 1920260.582230: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1216810 ofs=1662976 - <unknown>-12683 (-----) [005] .... 1920260.582234: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1175966 ofs=1667072 - <unknown>-12683 (-----) [005] .... 1920260.582235: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1449798 ofs=1671168 - <unknown>-12683 (-----) [005] .... 1920260.582236: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1273480 ofs=1675264 - <unknown>-12683 (-----) [005] .... 1920260.582236: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1152779 ofs=1679360 - <unknown>-12683 (-----) [005] .... 1920260.582237: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1272810 ofs=1683456 - <unknown>-12683 (-----) [005] .... 1920260.582237: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1248634 ofs=1687552 - <unknown>-12683 (-----) [005] .... 1920260.582237: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1203376 ofs=1691648 - <unknown>-12683 (-----) [005] .... 1920260.582238: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1138880 ofs=1695744 - <unknown>-12683 (-----) [005] .... 1920260.582238: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1344591 ofs=1699840 - <unknown>-12683 (-----) [005] .... 1920260.582239: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1416060 ofs=1703936 - <unknown>-12683 (-----) [005] .... 1920260.582246: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1128676 ofs=1708032 - <unknown>-12683 (-----) [005] .... 1920260.582247: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1301921 ofs=1712128 - <unknown>-12683 (-----) [005] .... 1920260.582248: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1384569 ofs=1716224 - <unknown>-12683 (-----) [005] .... 1920260.582248: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1249106 ofs=1720320 - <unknown>-12683 (-----) [005] .... 1920260.582249: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1206596 ofs=1724416 - <unknown>-12683 (-----) [005] .... 1920260.582249: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1429831 ofs=1728512 - <unknown>-12683 (-----) [005] .... 1920260.582252: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1107796 ofs=1732608 - <unknown>-12683 (-----) [005] .... 1920260.582255: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1098336 ofs=1736704 - <unknown>-12683 (-----) [005] .... 1920260.582255: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1230286 ofs=1740800 - <unknown>-12683 (-----) [005] .... 1920260.582256: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1100370 ofs=1744896 - <unknown>-12683 (-----) [005] .... 1920260.582256: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1241930 ofs=1748992 - <unknown>-12683 (-----) [005] .... 1920260.582257: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1366807 ofs=1753088 - <unknown>-12683 (-----) [005] .... 1920260.582257: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1136252 ofs=1757184 - <unknown>-12683 (-----) [005] .... 1920260.582258: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1274291 ofs=1761280 - <unknown>-12683 (-----) [005] .... 1920260.582258: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1254775 ofs=1765376 - <unknown>-12683 (-----) [005] .... 1920260.582259: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1194679 ofs=1769472 - <unknown>-12683 (-----) [005] .... 1920260.582262: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1177090 ofs=1773568 - <unknown>-12683 (-----) [005] .... 1920260.582263: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1343925 ofs=1777664 - <unknown>-12683 (-----) [005] .... 1920260.582263: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1159217 ofs=1781760 - <unknown>-12683 (-----) [005] .... 1920260.582263: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1435471 ofs=1785856 - <unknown>-12683 (-----) [005] .... 1920260.582264: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1435529 ofs=1789952 - <unknown>-12683 (-----) [004] .... 1920260.582524: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1181910 ofs=0 - <unknown>-12683 (-----) [004] .... 1920260.582528: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1212021 ofs=4096 - <unknown>-12683 (-----) [004] .... 1920260.582529: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1162778 ofs=8192 - <unknown>-12683 (-----) [004] .... 1920260.582529: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1107700 ofs=12288 - <unknown>-12683 (-----) [004] .... 1920260.583553: mm_filemap_add_to_page_cache: dev 0:64771 ino df31 page=0000000000000000 pfn=1093394 ofs=3399680 - <unknown>-12683 (-----) [004] .... 1920260.583984: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1121431 ofs=242503680 - <unknown>-12683 (-----) [004] .... 1920260.583986: mm_filemap_add_to_page_cache: dev 0:64771 ino 2 page=0000000000000000 pfn=1168551 ofs=13115392 - <unknown>-12683 (-----) [004] .... 1920260.584304: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1347409 ofs=0 - <unknown>-12683 (-----) [004] .... 1920260.584307: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1428681 ofs=4096 - <unknown>-12683 (-----) [004] .... 1920260.584307: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1259106 ofs=8192 - <unknown>-12683 (-----) [004] .... 1920260.584308: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1343229 ofs=12288 - <unknown>-12694 (-----) [005] .... 1920260.584622: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1098733 ofs=1531904 - <unknown>-12696 (-----) [006] .... 1920260.584626: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1331319 ofs=1536000 - <unknown>-12694 (-----) [005] .... 1920260.584626: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1278537 ofs=1540096 - <unknown>-12696 (-----) [006] .... 1920260.584631: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1492534 ofs=1544192 - <unknown>-12694 (-----) [005] .... 1920260.584636: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1460878 ofs=1548288 - <unknown>-12694 (-----) [005] .... 1920260.584640: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1092973 ofs=1552384 - <unknown>-12694 (-----) [005] .... 1920260.584641: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1103200 ofs=1556480 - <unknown>-12694 (-----) [005] .... 1920260.584642: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1257426 ofs=1560576 - <unknown>-12694 (-----) [005] .... 1920260.584642: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1219424 ofs=1564672 - <unknown>-12683 (-----) [004] .... 1920260.584660: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1279352 ofs=1568768 - <unknown>-12696 (-----) [006] .... 1920260.584662: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1260572 ofs=1572864 - <unknown>-12683 (-----) [004] .... 1920260.584663: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1225809 ofs=1576960 - <unknown>-12696 (-----) [006] .... 1920260.584665: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1350766 ofs=1585152 - <unknown>-12697 (-----) [007] .... 1920260.584666: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1107173 ofs=1581056 - <unknown>-12683 (-----) [004] .... 1920260.584668: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1305885 ofs=1589248 - <unknown>-12694 (-----) [005] .... 1920260.584669: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1293385 ofs=1593344 - <unknown>-12696 (-----) [006] .... 1920260.584670: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1173841 ofs=1597440 - <unknown>-12697 (-----) [007] .... 1920260.584670: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1080021 ofs=1601536 - <unknown>-12683 (-----) [004] .... 1920260.584673: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1147419 ofs=1605632 - <unknown>-12696 (-----) [006] .... 1920260.584673: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1252762 ofs=1609728 - <unknown>-12694 (-----) [005] .... 1920260.584674: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1323916 ofs=1613824 - <unknown>-12683 (-----) [004] .... 1920260.584675: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1155631 ofs=1617920 - <unknown>-12696 (-----) [006] .... 1920260.584676: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1449815 ofs=1622016 - <unknown>-12694 (-----) [005] .... 1920260.584678: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1227069 ofs=1626112 - <unknown>-12696 (-----) [006] .... 1920260.584680: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1317692 ofs=1630208 - <unknown>-12694 (-----) [005] .... 1920260.584681: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1492244 ofs=1634304 - <unknown>-12683 (-----) [004] .... 1920260.584682: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1241876 ofs=1638400 - <unknown>-12697 (-----) [007] .... 1920260.585446: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1402958 ofs=167936 - <unknown>-12697 (-----) [007] .... 1920260.585449: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1133263 ofs=172032 - <unknown>-12697 (-----) [007] .... 1920260.585450: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1295502 ofs=176128 - <unknown>-12697 (-----) [007] .... 1920260.585450: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1249495 ofs=180224 - <unknown>-12697 (-----) [007] .... 1920260.585451: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1237999 ofs=184320 - <unknown>-12697 (-----) [007] .... 1920260.585451: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1280965 ofs=188416 - <unknown>-12697 (-----) [007] .... 1920260.585454: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1208361 ofs=192512 - <unknown>-12697 (-----) [007] .... 1920260.585454: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1308840 ofs=196608 - <unknown>-12695 (-----) [004] .... 1920260.585455: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1138875 ofs=569344 - <unknown>-12695 (-----) [004] .... 1920260.585458: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1314886 ofs=573440 - <unknown>-12697 (-----) [007] .... 1920260.585458: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1242734 ofs=200704 - <unknown>-12695 (-----) [004] .... 1920260.585458: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1447386 ofs=577536 - <unknown>-12697 (-----) [007] .... 1920260.585459: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1241302 ofs=204800 - <unknown>-12695 (-----) [004] .... 1920260.585459: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1328663 ofs=581632 - <unknown>-12697 (-----) [007] .... 1920260.585459: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1476101 ofs=208896 - <unknown>-12695 (-----) [004] .... 1920260.585460: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1209461 ofs=585728 - <unknown>-12697 (-----) [007] .... 1920260.585460: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1080147 ofs=212992 - <unknown>-12697 (-----) [007] .... 1920260.585461: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1128509 ofs=217088 - <unknown>-12697 (-----) [007] .... 1920260.585461: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1371915 ofs=221184 - <unknown>-12697 (-----) [007] .... 1920260.585461: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1264015 ofs=225280 - <unknown>-12697 (-----) [007] .... 1920260.585462: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1211695 ofs=229376 - <unknown>-12697 (-----) [007] .... 1920260.585462: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1150386 ofs=233472 - <unknown>-12697 (-----) [007] .... 1920260.585463: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1135747 ofs=237568 - <unknown>-12697 (-----) [007] .... 1920260.585463: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1128230 ofs=241664 - <unknown>-12697 (-----) [007] .... 1920260.585464: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1155451 ofs=245760 - <unknown>-12697 (-----) [007] .... 1920260.585465: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1246841 ofs=249856 - <unknown>-12697 (-----) [007] .... 1920260.585465: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1462971 ofs=253952 - <unknown>-12697 (-----) [007] .... 1920260.585466: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1131333 ofs=258048 - <unknown>-12697 (-----) [007] .... 1920260.585466: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1289407 ofs=262144 - <unknown>-12695 (-----) [004] .... 1920260.585467: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1134730 ofs=589824 - <unknown>-12697 (-----) [007] .... 1920260.585467: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1289873 ofs=266240 - <unknown>-12697 (-----) [007] .... 1920260.585468: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1448734 ofs=270336 - <unknown>-12695 (-----) [004] .... 1920260.585468: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1129776 ofs=593920 - <unknown>-12697 (-----) [007] .... 1920260.585468: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1524090 ofs=274432 - <unknown>-12695 (-----) [004] .... 1920260.585468: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1399725 ofs=598016 - <unknown>-12697 (-----) [007] .... 1920260.585469: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1524081 ofs=278528 - <unknown>-12695 (-----) [004] .... 1920260.585469: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1276535 ofs=602112 - <unknown>-12697 (-----) [007] .... 1920260.585469: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1524060 ofs=282624 - <unknown>-12695 (-----) [004] .... 1920260.585470: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1449847 ofs=606208 - <unknown>-12697 (-----) [007] .... 1920260.585470: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1158944 ofs=286720 - <unknown>-12695 (-----) [004] .... 1920260.585470: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1384536 ofs=610304 - <unknown>-12697 (-----) [007] .... 1920260.585470: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1116785 ofs=290816 - <unknown>-12695 (-----) [004] .... 1920260.585471: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1308118 ofs=614400 - <unknown>-12697 (-----) [007] .... 1920260.585471: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1448669 ofs=294912 - <unknown>-12695 (-----) [004] .... 1920260.585471: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1227050 ofs=618496 - <unknown>-12695 (-----) [004] .... 1920260.585473: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1289324 ofs=622592 - <unknown>-12695 (-----) [004] .... 1920260.585473: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1187869 ofs=626688 - <unknown>-12695 (-----) [004] .... 1920260.585474: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1400523 ofs=630784 - <unknown>-12695 (-----) [004] .... 1920260.585474: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1344176 ofs=634880 - <unknown>-12695 (-----) [004] .... 1920260.585475: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1092871 ofs=638976 - <unknown>-12695 (-----) [004] .... 1920260.585475: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1092021 ofs=643072 - <unknown>-12695 (-----) [004] .... 1920260.585476: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1198169 ofs=647168 - <unknown>-12695 (-----) [004] .... 1920260.585476: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1371540 ofs=651264 - <unknown>-12683 (-----) [005] .... 1920260.585476: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1195003 ofs=348160 - <unknown>-12695 (-----) [004] .... 1920260.585477: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1228787 ofs=655360 - <unknown>-12695 (-----) [004] .... 1920260.585477: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1236123 ofs=659456 - <unknown>-12695 (-----) [004] .... 1920260.585477: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1137213 ofs=663552 - <unknown>-12695 (-----) [004] .... 1920260.585478: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1294618 ofs=667648 - <unknown>-12695 (-----) [004] .... 1920260.585478: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1241048 ofs=671744 - <unknown>-12695 (-----) [004] .... 1920260.585479: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1228779 ofs=675840 - <unknown>-12683 (-----) [005] .... 1920260.585479: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1199292 ofs=352256 - <unknown>-12683 (-----) [005] .... 1920260.585480: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1200861 ofs=356352 - <unknown>-12695 (-----) [004] .... 1920260.585480: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1309572 ofs=679936 - <unknown>-12683 (-----) [005] .... 1920260.585480: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1215770 ofs=360448 - <unknown>-12695 (-----) [004] .... 1920260.585481: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1409002 ofs=684032 - <unknown>-12683 (-----) [005] .... 1920260.585481: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1151883 ofs=364544 - <unknown>-12695 (-----) [004] .... 1920260.585481: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1103729 ofs=688128 - <unknown>-12683 (-----) [005] .... 1920260.585482: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1468126 ofs=368640 - <unknown>-12695 (-----) [004] .... 1920260.585482: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1162720 ofs=692224 - <unknown>-12683 (-----) [005] .... 1920260.585482: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1251672 ofs=372736 - <unknown>-12695 (-----) [004] .... 1920260.585482: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1199221 ofs=696320 - <unknown>-12683 (-----) [005] .... 1920260.585483: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1283325 ofs=376832 - <unknown>-12683 (-----) [005] .... 1920260.585483: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1190489 ofs=380928 - <unknown>-12683 (-----) [005] .... 1920260.585484: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1489117 ofs=385024 - <unknown>-12683 (-----) [005] .... 1920260.585484: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1273899 ofs=389120 - <unknown>-12683 (-----) [005] .... 1920260.585485: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1274459 ofs=393216 - <unknown>-12683 (-----) [005] .... 1920260.585486: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1316649 ofs=397312 - <unknown>-12683 (-----) [005] .... 1920260.585491: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1375678 ofs=401408 - <unknown>-12683 (-----) [005] .... 1920260.585491: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1483317 ofs=405504 - <unknown>-12683 (-----) [005] .... 1920260.585492: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1240286 ofs=409600 - <unknown>-12683 (-----) [005] .... 1920260.585492: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1131345 ofs=413696 - <unknown>-12683 (-----) [005] .... 1920260.585493: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1200483 ofs=417792 - <unknown>-12683 (-----) [005] .... 1920260.585493: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1384693 ofs=421888 - <unknown>-12683 (-----) [005] .... 1920260.585493: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1161385 ofs=425984 - <unknown>-12683 (-----) [005] .... 1920260.585494: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1452025 ofs=430080 - <unknown>-12683 (-----) [005] .... 1920260.585495: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1253654 ofs=434176 - <unknown>-12683 (-----) [005] .... 1920260.585495: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1116697 ofs=438272 - <unknown>-12683 (-----) [005] .... 1920260.585495: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1432645 ofs=442368 - <unknown>-12694 (-----) [006] .... 1920260.585495: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1337397 ofs=16384 - <unknown>-12683 (-----) [005] .... 1920260.585496: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1304229 ofs=446464 - <unknown>-12683 (-----) [005] .... 1920260.585496: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1419147 ofs=450560 - <unknown>-12683 (-----) [005] .... 1920260.585498: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1349246 ofs=454656 - <unknown>-12683 (-----) [005] .... 1920260.585499: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1128519 ofs=458752 - <unknown>-12683 (-----) [005] .... 1920260.585499: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1125168 ofs=462848 - <unknown>-12694 (-----) [006] .... 1920260.585509: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1081031 ofs=20480 - <unknown>-12694 (-----) [006] .... 1920260.585509: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1293022 ofs=24576 - <unknown>-12694 (-----) [006] .... 1920260.585510: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1113007 ofs=28672 - <unknown>-12694 (-----) [006] .... 1920260.585510: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1339312 ofs=32768 - <unknown>-12694 (-----) [006] .... 1920260.585511: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1412311 ofs=36864 - <unknown>-12694 (-----) [006] .... 1920260.585511: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1260960 ofs=40960 - <unknown>-12694 (-----) [006] .... 1920260.585512: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1189529 ofs=45056 - <unknown>-12694 (-----) [006] .... 1920260.585512: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1412184 ofs=49152 - <unknown>-12694 (-----) [006] .... 1920260.585513: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1481227 ofs=53248 - <unknown>-12694 (-----) [006] .... 1920260.585513: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1455940 ofs=57344 - <unknown>-12694 (-----) [006] .... 1920260.585514: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1299132 ofs=61440 - <unknown>-12694 (-----) [006] .... 1920260.585514: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1337375 ofs=65536 - <unknown>-12694 (-----) [006] .... 1920260.585529: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1328742 ofs=69632 - <unknown>-12694 (-----) [006] .... 1920260.585529: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1315646 ofs=73728 - <unknown>-12694 (-----) [006] .... 1920260.585531: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1225475 ofs=77824 - <unknown>-12694 (-----) [006] .... 1920260.585531: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1146097 ofs=81920 - <unknown>-12694 (-----) [006] .... 1920260.585532: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1318775 ofs=86016 - <unknown>-12694 (-----) [006] .... 1920260.585532: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1448391 ofs=90112 - <unknown>-12694 (-----) [006] .... 1920260.585532: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1441412 ofs=94208 - <unknown>-12694 (-----) [006] .... 1920260.585533: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1138111 ofs=98304 - <unknown>-12694 (-----) [006] .... 1920260.585533: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1143223 ofs=102400 - <unknown>-12683 (-----) [005] .... 1920260.585534: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1079876 ofs=466944 - <unknown>-12694 (-----) [006] .... 1920260.585534: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1447637 ofs=106496 - <unknown>-12694 (-----) [006] .... 1920260.585534: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1220585 ofs=110592 - <unknown>-12694 (-----) [006] .... 1920260.585535: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1449051 ofs=114688 - <unknown>-12694 (-----) [006] .... 1920260.585535: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1313180 ofs=118784 - <unknown>-12694 (-----) [006] .... 1920260.585535: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1313166 ofs=122880 - <unknown>-12694 (-----) [006] .... 1920260.585536: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1313154 ofs=126976 - <unknown>-12683 (-----) [005] .... 1920260.585536: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1218394 ofs=471040 - <unknown>-12694 (-----) [006] .... 1920260.585536: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1144047 ofs=131072 - <unknown>-12683 (-----) [005] .... 1920260.585537: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1218579 ofs=475136 - <unknown>-12694 (-----) [006] .... 1920260.585543: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1241332 ofs=135168 - <unknown>-12694 (-----) [006] .... 1920260.585543: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1097199 ofs=139264 - <unknown>-12694 (-----) [006] .... 1920260.585545: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1214197 ofs=143360 - <unknown>-12694 (-----) [006] .... 1920260.585645: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1197633 ofs=147456 - <unknown>-12694 (-----) [006] .... 1920260.585647: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1311536 ofs=151552 - <unknown>-12694 (-----) [006] .... 1920260.585647: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1322952 ofs=155648 - <unknown>-12694 (-----) [006] .... 1920260.585647: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1346974 ofs=159744 - <unknown>-12694 (-----) [006] .... 1920260.585648: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1257232 ofs=163840 - <unknown>-12695 (-----) [004] .... 1920260.586355: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1204484 ofs=700416 - <unknown>-12695 (-----) [004] .... 1920260.586357: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1326426 ofs=704512 - <unknown>-12695 (-----) [004] .... 1920260.586358: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1151808 ofs=708608 - <unknown>-12695 (-----) [004] .... 1920260.586358: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1209422 ofs=712704 - <unknown>-12695 (-----) [004] .... 1920260.586359: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1408387 ofs=716800 - <unknown>-12695 (-----) [004] .... 1920260.586359: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1197336 ofs=720896 - <unknown>-12695 (-----) [004] .... 1920260.586363: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1205652 ofs=724992 - <unknown>-12695 (-----) [004] .... 1920260.586363: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1133421 ofs=729088 - <unknown>-12695 (-----) [004] .... 1920260.586364: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1092173 ofs=733184 - <unknown>-12695 (-----) [004] .... 1920260.586365: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1124430 ofs=737280 - <unknown>-12695 (-----) [004] .... 1920260.586365: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1143926 ofs=741376 - <unknown>-12695 (-----) [004] .... 1920260.586366: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1090109 ofs=745472 - <unknown>-12695 (-----) [004] .... 1920260.586366: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1102012 ofs=749568 - <unknown>-12695 (-----) [004] .... 1920260.586367: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1154930 ofs=753664 - <unknown>-12695 (-----) [004] .... 1920260.586368: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1132993 ofs=757760 - <unknown>-12695 (-----) [004] .... 1920260.586369: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1430780 ofs=761856 - <unknown>-12695 (-----) [004] .... 1920260.586369: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1197452 ofs=765952 - <unknown>-12695 (-----) [004] .... 1920260.586369: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1075111 ofs=770048 - <unknown>-12695 (-----) [004] .... 1920260.586370: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1275616 ofs=774144 - <unknown>-12695 (-----) [004] .... 1920260.586370: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1444981 ofs=778240 - <unknown>-12695 (-----) [004] .... 1920260.586371: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1452592 ofs=782336 - <unknown>-12695 (-----) [004] .... 1920260.586374: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1102857 ofs=786432 - <unknown>-12695 (-----) [004] .... 1920260.586376: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1406969 ofs=790528 - <unknown>-12695 (-----) [004] .... 1920260.586378: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1522553 ofs=794624 - <unknown>-12695 (-----) [004] .... 1920260.586378: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1260771 ofs=798720 - <unknown>-12695 (-----) [004] .... 1920260.586379: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1474649 ofs=802816 - <unknown>-12695 (-----) [004] .... 1920260.586379: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1268708 ofs=806912 - <unknown>-12695 (-----) [004] .... 1920260.586379: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1346144 ofs=811008 - <unknown>-12695 (-----) [004] .... 1920260.586380: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1081167 ofs=815104 - <unknown>-12695 (-----) [004] .... 1920260.586380: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1137677 ofs=819200 - <unknown>-12695 (-----) [004] .... 1920260.586381: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1161175 ofs=823296 - <unknown>-12695 (-----) [004] .... 1920260.586381: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1461331 ofs=827392 - <unknown>-12695 (-----) [004] .... 1920260.586492: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1347219 ofs=831488 - <unknown>-12695 (-----) [004] .... 1920260.586494: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1290004 ofs=835584 - <unknown>-12695 (-----) [004] .... 1920260.586494: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1299174 ofs=839680 - <unknown>-12695 (-----) [004] .... 1920260.586496: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1317595 ofs=843776 - <unknown>-12695 (-----) [004] .... 1920260.586496: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1484924 ofs=847872 - <unknown>-12695 (-----) [004] .... 1920260.586497: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1169920 ofs=851968 - <unknown>-12695 (-----) [004] .... 1920260.586501: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1359189 ofs=856064 - <unknown>-12695 (-----) [004] .... 1920260.586501: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1307842 ofs=860160 - <unknown>-12695 (-----) [004] .... 1920260.586502: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1237858 ofs=864256 - <unknown>-12695 (-----) [004] .... 1920260.586502: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1189461 ofs=868352 - <unknown>-12695 (-----) [004] .... 1920260.586503: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1223232 ofs=872448 - <unknown>-12695 (-----) [004] .... 1920260.586503: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1104076 ofs=876544 - <unknown>-12695 (-----) [004] .... 1920260.586504: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1079223 ofs=880640 - <unknown>-12695 (-----) [004] .... 1920260.586504: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1092537 ofs=884736 - <unknown>-12695 (-----) [004] .... 1920260.586505: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1353960 ofs=888832 - <unknown>-12695 (-----) [004] .... 1920260.586505: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1346330 ofs=892928 - <unknown>-12695 (-----) [004] .... 1920260.586506: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1345764 ofs=897024 - <unknown>-12695 (-----) [004] .... 1920260.586507: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1363913 ofs=901120 - <unknown>-12695 (-----) [004] .... 1920260.586508: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1319570 ofs=905216 - <unknown>-12695 (-----) [004] .... 1920260.586508: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1367024 ofs=909312 - <unknown>-12695 (-----) [004] .... 1920260.586508: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1333808 ofs=913408 - <unknown>-12695 (-----) [004] .... 1920260.586509: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1158627 ofs=917504 - <unknown>-12695 (-----) [004] .... 1920260.586509: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1300368 ofs=921600 - <unknown>-12695 (-----) [004] .... 1920260.586510: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1245363 ofs=925696 - <unknown>-12695 (-----) [004] .... 1920260.586510: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1345609 ofs=929792 - <unknown>-12695 (-----) [004] .... 1920260.586510: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1393826 ofs=933888 - <unknown>-12695 (-----) [004] .... 1920260.586511: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1200552 ofs=937984 - <unknown>-12695 (-----) [004] .... 1920260.586511: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1170885 ofs=942080 - <unknown>-12695 (-----) [004] .... 1920260.586512: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1536209 ofs=946176 - <unknown>-12695 (-----) [004] .... 1920260.586512: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1189630 ofs=950272 - <unknown>-12695 (-----) [004] .... 1920260.586513: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1121010 ofs=954368 - <unknown>-12695 (-----) [004] .... 1920260.586514: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1324474 ofs=958464 - <unknown>-12697 (-----) [007] .... 1920260.586578: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1129628 ofs=299008 - <unknown>-12697 (-----) [007] .... 1920260.586579: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1307120 ofs=303104 - <unknown>-12697 (-----) [007] .... 1920260.586580: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1347284 ofs=307200 - <unknown>-12697 (-----) [007] .... 1920260.586580: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1312996 ofs=311296 - <unknown>-12697 (-----) [007] .... 1920260.586581: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1170623 ofs=315392 - <unknown>-12697 (-----) [007] .... 1920260.586581: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1359281 ofs=319488 - <unknown>-12697 (-----) [007] .... 1920260.586582: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1180021 ofs=323584 - <unknown>-12697 (-----) [007] .... 1920260.586582: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1195728 ofs=327680 - <unknown>-12697 (-----) [007] .... 1920260.586582: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1163642 ofs=331776 - <unknown>-12697 (-----) [007] .... 1920260.586587: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1152538 ofs=335872 - <unknown>-12697 (-----) [007] .... 1920260.586589: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1345922 ofs=339968 - <unknown>-12697 (-----) [007] .... 1920260.586589: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1343604 ofs=344064 - <unknown>-12697 (-----) [007] .... 1920260.586721: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1399371 ofs=479232 - <unknown>-12697 (-----) [007] .... 1920260.586723: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1106549 ofs=483328 - <unknown>-12697 (-----) [007] .... 1920260.586724: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1331546 ofs=487424 - <unknown>-12697 (-----) [007] .... 1920260.586724: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1299299 ofs=491520 - <unknown>-12697 (-----) [007] .... 1920260.586725: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1288883 ofs=495616 - <unknown>-12697 (-----) [007] .... 1920260.586725: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1399049 ofs=499712 - <unknown>-12697 (-----) [007] .... 1920260.586726: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1146931 ofs=503808 - <unknown>-12697 (-----) [007] .... 1920260.586726: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1296592 ofs=507904 - <unknown>-12697 (-----) [007] .... 1920260.586727: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1468397 ofs=512000 - <unknown>-12697 (-----) [007] .... 1920260.586727: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1215698 ofs=516096 - <unknown>-12697 (-----) [007] .... 1920260.586727: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1177341 ofs=520192 - <unknown>-12697 (-----) [007] .... 1920260.586731: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1189162 ofs=524288 - <unknown>-12697 (-----) [007] .... 1920260.586732: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1435997 ofs=528384 - <unknown>-12697 (-----) [007] .... 1920260.586732: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1209896 ofs=532480 - <unknown>-12697 (-----) [007] .... 1920260.586733: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1255888 ofs=536576 - <unknown>-12697 (-----) [007] .... 1920260.586734: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1234200 ofs=540672 - <unknown>-12697 (-----) [007] .... 1920260.586734: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1422854 ofs=544768 - <unknown>-12697 (-----) [007] .... 1920260.586735: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1435794 ofs=548864 - <unknown>-12697 (-----) [007] .... 1920260.586735: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1236279 ofs=552960 - <unknown>-12697 (-----) [007] .... 1920260.586736: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1485732 ofs=557056 - <unknown>-12683 (-----) [005] .... 1920260.586743: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1417198 ofs=561152 - <unknown>-12683 (-----) [005] .... 1920260.586746: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1469450 ofs=565248 - <unknown>-12696 (-----) [004] .... 1920260.587465: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1489023 ofs=1040384 - <unknown>-12696 (-----) [004] .... 1920260.587469: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1449498 ofs=1044480 - <unknown>-12696 (-----) [004] .... 1920260.587469: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1447737 ofs=1048576 - <unknown>-12696 (-----) [004] .... 1920260.587470: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1124530 ofs=1052672 - <unknown>-12696 (-----) [004] .... 1920260.587476: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1246743 ofs=1056768 - <unknown>-12696 (-----) [004] .... 1920260.587476: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1441927 ofs=1060864 - <unknown>-12696 (-----) [004] .... 1920260.587477: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1280581 ofs=1064960 - <unknown>-12696 (-----) [004] .... 1920260.587477: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1289438 ofs=1069056 - <unknown>-12696 (-----) [004] .... 1920260.587477: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1464236 ofs=1073152 - <unknown>-12696 (-----) [004] .... 1920260.587478: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1125808 ofs=1077248 - <unknown>-12696 (-----) [004] .... 1920260.587478: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1329385 ofs=1081344 - <unknown>-12696 (-----) [004] .... 1920260.587480: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1314093 ofs=1085440 - <unknown>-12696 (-----) [004] .... 1920260.587480: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1201837 ofs=1089536 - <unknown>-12696 (-----) [004] .... 1920260.587481: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1327734 ofs=1093632 - <unknown>-12696 (-----) [004] .... 1920260.587481: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1406568 ofs=1097728 - <unknown>-12696 (-----) [004] .... 1920260.587481: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1331873 ofs=1101824 - <unknown>-12696 (-----) [004] .... 1920260.587482: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1331898 ofs=1105920 - <unknown>-12696 (-----) [004] .... 1920260.587482: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1331917 ofs=1110016 - <unknown>-12696 (-----) [004] .... 1920260.587483: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1332091 ofs=1114112 - <unknown>-12696 (-----) [004] .... 1920260.587483: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1108186 ofs=1118208 - <unknown>-12696 (-----) [004] .... 1920260.587486: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1182631 ofs=1122304 - <unknown>-12696 (-----) [004] .... 1920260.587486: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1085941 ofs=1126400 - <unknown>-12696 (-----) [004] .... 1920260.587487: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1433982 ofs=1130496 - <unknown>-12696 (-----) [004] .... 1920260.587487: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1387028 ofs=1134592 - <unknown>-12696 (-----) [004] .... 1920260.587488: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1353117 ofs=1138688 - <unknown>-12696 (-----) [004] .... 1920260.587489: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1352364 ofs=1142784 - <unknown>-12696 (-----) [004] .... 1920260.587489: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1144513 ofs=1146880 - <unknown>-12696 (-----) [004] .... 1920260.587490: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1403984 ofs=1150976 - <unknown>-12696 (-----) [004] .... 1920260.587490: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1278970 ofs=1155072 - <unknown>-12696 (-----) [004] .... 1920260.587491: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1326743 ofs=1159168 - <unknown>-12696 (-----) [004] .... 1920260.587491: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1221809 ofs=1163264 - <unknown>-12696 (-----) [004] .... 1920260.587492: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1268668 ofs=1167360 - <unknown>-12695 (-----) [005] .... 1920260.587502: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1074544 ofs=962560 - <unknown>-12695 (-----) [005] .... 1920260.587506: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1074294 ofs=966656 - <unknown>-12695 (-----) [005] .... 1920260.587506: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1075097 ofs=970752 - <unknown>-12695 (-----) [005] .... 1920260.587507: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1162407 ofs=974848 - <unknown>-12695 (-----) [005] .... 1920260.587507: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1141370 ofs=978944 - <unknown>-12695 (-----) [005] .... 1920260.587508: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1306487 ofs=983040 - <unknown>-12695 (-----) [005] .... 1920260.587508: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1306434 ofs=987136 - <unknown>-12695 (-----) [005] .... 1920260.587514: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1306347 ofs=991232 - <unknown>-12695 (-----) [005] .... 1920260.587514: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1306247 ofs=995328 - <unknown>-12695 (-----) [005] .... 1920260.587515: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1306195 ofs=999424 - <unknown>-12695 (-----) [005] .... 1920260.587516: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1306039 ofs=1003520 - <unknown>-12695 (-----) [005] .... 1920260.587516: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1305983 ofs=1007616 - <unknown>-12694 (-----) [006] .... 1920260.587701: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1216391 ofs=1171456 - <unknown>-12694 (-----) [006] .... 1920260.587705: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1262462 ofs=1175552 - <unknown>-12694 (-----) [006] .... 1920260.587706: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1358114 ofs=1179648 - <unknown>-12694 (-----) [006] .... 1920260.587706: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1357898 ofs=1183744 - <unknown>-12694 (-----) [006] .... 1920260.587707: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1237003 ofs=1187840 - <unknown>-12694 (-----) [006] .... 1920260.587707: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1126319 ofs=1191936 - <unknown>-12694 (-----) [006] .... 1920260.587708: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1415489 ofs=1196032 - <unknown>-12694 (-----) [006] .... 1920260.587708: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1279558 ofs=1200128 - <unknown>-12694 (-----) [006] .... 1920260.587708: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1434022 ofs=1204224 - <unknown>-12694 (-----) [006] .... 1920260.587709: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1220130 ofs=1208320 - <unknown>-12694 (-----) [006] .... 1920260.587710: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1163037 ofs=1212416 - <unknown>-12694 (-----) [006] .... 1920260.587711: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1404501 ofs=1216512 - <unknown>-12694 (-----) [006] .... 1920260.587711: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1406287 ofs=1220608 - <unknown>-12697 (-----) [007] .... 1920260.588132: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1355143 ofs=1376256 - <unknown>-12697 (-----) [007] .... 1920260.588136: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1213923 ofs=1380352 - <unknown>-12697 (-----) [007] .... 1920260.588136: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1243190 ofs=1384448 - <unknown>-12697 (-----) [007] .... 1920260.588143: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1300698 ofs=1388544 - <unknown>-12697 (-----) [007] .... 1920260.588144: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1482568 ofs=1392640 - <unknown>-12697 (-----) [007] .... 1920260.588144: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1461789 ofs=1396736 - <unknown>-12697 (-----) [007] .... 1920260.588145: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1242314 ofs=1400832 - <unknown>-12697 (-----) [007] .... 1920260.588145: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1471996 ofs=1404928 - <unknown>-12697 (-----) [007] .... 1920260.588146: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1242742 ofs=1409024 - <unknown>-12697 (-----) [007] .... 1920260.588146: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1242579 ofs=1413120 - <unknown>-12697 (-----) [007] .... 1920260.588148: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1242553 ofs=1417216 - <unknown>-12697 (-----) [007] .... 1920260.588148: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1457332 ofs=1421312 - <unknown>-12697 (-----) [007] .... 1920260.588149: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1315431 ofs=1425408 - <unknown>-12697 (-----) [007] .... 1920260.588149: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1080653 ofs=1429504 - <unknown>-12697 (-----) [007] .... 1920260.588149: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1324174 ofs=1433600 - <unknown>-12697 (-----) [007] .... 1920260.588150: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1324142 ofs=1437696 - <unknown>-12697 (-----) [007] .... 1920260.588150: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1157760 ofs=1441792 - <unknown>-12697 (-----) [007] .... 1920260.588151: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1075059 ofs=1445888 - <unknown>-12683 (-----) [006] .... 1920260.589785: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1279192 ofs=1486848 - <unknown>-12683 (-----) [006] .... 1920260.589790: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1278527 ofs=1490944 - <unknown>-12683 (-----) [006] .... 1920260.589791: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1091778 ofs=1495040 - <unknown>-12683 (-----) [006] .... 1920260.589791: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1339447 ofs=1499136 - <unknown>-12683 (-----) [006] .... 1920260.589792: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1254007 ofs=1503232 - <unknown>-12683 (-----) [006] .... 1920260.589793: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1115173 ofs=1507328 - <unknown>-12683 (-----) [006] .... 1920260.589793: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1393985 ofs=1511424 - <unknown>-12683 (-----) [006] .... 1920260.589794: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1369123 ofs=1515520 - <unknown>-12683 (-----) [006] .... 1920260.589794: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1314257 ofs=1519616 - <unknown>-12683 (-----) [006] .... 1920260.589802: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1404487 ofs=1523712 - <unknown>-12683 (-----) [006] .... 1920260.589803: mm_filemap_add_to_page_cache: dev 0:64771 ino e745 page=0000000000000000 pfn=1354554 ofs=1527808 - <unknown>-12683 (-----) [006] .... 1920260.594312: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1141445 ofs=9801728 - <unknown>-12683 (-----) [006] .... 1920260.594322: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1323774 ofs=231460864 - <unknown>-12683 (-----) [006] .... 1920260.594326: mm_filemap_add_to_page_cache: dev 0:64771 ino 2 page=0000000000000000 pfn=1323772 ofs=10993664 - <unknown>-12683 (-----) [006] .... 1920260.595212: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1481305 ofs=9805824 - <unknown>-12683 (-----) [006] .... 1920260.595214: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1481306 ofs=9809920 - <unknown>-12683 (-----) [006] .... 1920260.595214: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1481316 ofs=9814016 - <unknown>-12683 (-----) [006] .... 1920260.595215: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1481340 ofs=9818112 - <unknown>-12683 (-----) [006] .... 1920260.595216: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1394587 ofs=9822208 - <unknown>-12683 (-----) [006] .... 1920260.595216: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103455 ofs=9826304 - <unknown>-12683 (-----) [006] .... 1920260.595217: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103271 ofs=9830400 - <unknown>-12683 (-----) [006] .... 1920260.595218: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103168 ofs=9834496 - <unknown>-12683 (-----) [006] .... 1920260.595218: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103145 ofs=9838592 - <unknown>-12683 (-----) [006] .... 1920260.595219: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103115 ofs=9842688 - <unknown>-12683 (-----) [006] .... 1920260.595222: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103057 ofs=9846784 - <unknown>-12683 (-----) [006] .... 1920260.595222: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1331958 ofs=9850880 - <unknown>-12683 (-----) [006] .... 1920260.595227: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1356305 ofs=9854976 - <unknown>-12683 (-----) [006] .... 1920260.595228: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103708 ofs=9859072 - <unknown>-12683 (-----) [006] .... 1920260.595228: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1099286 ofs=9863168 - <unknown>-12683 (-----) [006] .... 1920260.595229: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1435190 ofs=9867264 - <unknown>-12683 (-----) [006] .... 1920260.595229: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1395504 ofs=9871360 - <unknown>-12683 (-----) [006] .... 1920260.595230: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1352916 ofs=9875456 - <unknown>-12683 (-----) [006] .... 1920260.595231: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1255529 ofs=9879552 - <unknown>-12683 (-----) [006] .... 1920260.595231: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1336145 ofs=9883648 - <unknown>-12683 (-----) [006] .... 1920260.595232: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1334143 ofs=9887744 - <unknown>-12683 (-----) [006] .... 1920260.595232: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1328548 ofs=9891840 - <unknown>-12683 (-----) [006] .... 1920260.595232: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1222215 ofs=9895936 - <unknown>-12683 (-----) [006] .... 1920260.595233: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1461056 ofs=9900032 - <unknown>-12683 (-----) [006] .... 1920260.595234: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1228276 ofs=9904128 - <unknown>-12683 (-----) [006] .... 1920260.595235: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1151188 ofs=9908224 - <unknown>-12683 (-----) [006] .... 1920260.595236: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1443605 ofs=9912320 - <unknown>-12683 (-----) [006] .... 1920260.595236: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1146821 ofs=9916416 - <unknown>-12683 (-----) [006] .... 1920260.595237: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103669 ofs=9920512 - <unknown>-12683 (-----) [006] .... 1920260.595238: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103744 ofs=9924608 - <unknown>-12683 (-----) [006] .... 1920260.595238: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1103868 ofs=9928704 - <unknown>-12683 (-----) [006] .... 1920260.595789: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1465942 ofs=15855616 - <unknown>-12683 (-----) [006] .... 1920260.595792: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1323712 ofs=261189632 - <unknown>-12683 (-----) [006] .... 1920260.595998: mm_filemap_add_to_page_cache: dev 0:64771 ino 1 page=0000000000000000 pfn=1323701 ofs=262094848 - <unknown>-12683 (-----) [006] .... 1920260.596191: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1222287 ofs=15859712 - <unknown>-12683 (-----) [006] .... 1920260.596192: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1213146 ofs=15863808 - <unknown>-12683 (-----) [006] .... 1920260.596192: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1310396 ofs=15867904 - <unknown>-12683 (-----) [006] .... 1920260.596193: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1310177 ofs=15872000 - <unknown>-12683 (-----) [006] .... 1920260.596194: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1187914 ofs=15876096 - <unknown>-12683 (-----) [006] .... 1920260.596195: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1322409 ofs=15880192 - <unknown>-12683 (-----) [006] .... 1920260.596195: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1282484 ofs=15884288 - <unknown>-12683 (-----) [006] .... 1920260.596200: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1097245 ofs=15888384 - <unknown>-12683 (-----) [006] .... 1920260.596200: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1416816 ofs=15892480 - <unknown>-12683 (-----) [006] .... 1920260.596201: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1257125 ofs=15896576 - <unknown>-12683 (-----) [006] .... 1920260.596201: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1403527 ofs=15900672 - <unknown>-12683 (-----) [006] .... 1920260.596202: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1218006 ofs=15904768 - <unknown>-12683 (-----) [006] .... 1920260.596202: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1153893 ofs=15908864 - <unknown>-12683 (-----) [006] .... 1920260.596202: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1328023 ofs=15912960 - <unknown>-12683 (-----) [006] .... 1920260.596203: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1465412 ofs=15917056 - <unknown>-12683 (-----) [006] .... 1920260.596203: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1092448 ofs=15921152 - <unknown>-12683 (-----) [006] .... 1920260.596204: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1239220 ofs=15925248 - <unknown>-12683 (-----) [006] .... 1920260.596204: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1276491 ofs=15929344 - <unknown>-12683 (-----) [006] .... 1920260.596205: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1262240 ofs=15933440 - <unknown>-12683 (-----) [006] .... 1920260.596206: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1323793 ofs=15937536 - <unknown>-12683 (-----) [006] .... 1920260.596206: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1074937 ofs=15941632 - <unknown>-12683 (-----) [006] .... 1920260.596207: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1311157 ofs=15945728 - <unknown>-12683 (-----) [006] .... 1920260.596207: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1308442 ofs=15949824 - <unknown>-12683 (-----) [006] .... 1920260.596210: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1467709 ofs=15953920 - <unknown>-12683 (-----) [006] .... 1920260.596211: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1394299 ofs=15958016 - <unknown>-12683 (-----) [004] .... 1920260.612586: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1316156 ofs=344064 - <unknown>-12683 (-----) [004] .... 1920260.612591: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1406323 ofs=348160 - <unknown>-12683 (-----) [004] .... 1920260.612601: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1216972 ofs=352256 - <unknown>-12683 (-----) [004] .... 1920260.612605: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1271924 ofs=356352 - <unknown>-12683 (-----) [004] .... 1920260.612605: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1369225 ofs=360448 - <unknown>-12683 (-----) [004] .... 1920260.612608: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1318474 ofs=364544 - <unknown>-12683 (-----) [004] .... 1920260.612609: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1227283 ofs=368640 - <unknown>-12683 (-----) [004] .... 1920260.612613: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1364376 ofs=372736 - <unknown>-12683 (-----) [004] .... 1920260.612613: mm_filemap_add_to_page_cache: dev 0:64771 ino 180a page=0000000000000000 pfn=1073400 ofs=376832 diff --git a/startop/scripts/iorap/test_fixtures/compiler/test_result_with_duration.TraceFile.pb b/startop/scripts/iorap/test_fixtures/compiler/test_result_with_duration.TraceFile.pb Binary files differdeleted file mode 100644 index ab3df45f8c54..000000000000 --- a/startop/scripts/iorap/test_fixtures/compiler/test_result_with_duration.TraceFile.pb +++ /dev/null diff --git a/startop/scripts/iorap/test_fixtures/compiler/test_result_without_duration.TraceFile.pb b/startop/scripts/iorap/test_fixtures/compiler/test_result_without_duration.TraceFile.pb Binary files differdeleted file mode 100644 index 17cb11662172..000000000000 --- a/startop/scripts/iorap/test_fixtures/compiler/test_result_without_duration.TraceFile.pb +++ /dev/null diff --git a/startop/scripts/lib/cmd_utils.py b/startop/scripts/lib/cmd_utils.py deleted file mode 100644 index 6071f145fe1d..000000000000 --- a/startop/scripts/lib/cmd_utils.py +++ /dev/null @@ -1,184 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. - -"""Helper util libraries for command line operations.""" - -import asyncio -import sys -import time -from typing import Tuple, Optional, List - -import lib.print_utils as print_utils - -TIMEOUT = 50 -SIMULATE = False - -def run_command_nofail(cmd: List[str], **kwargs) -> None: - """Runs cmd list with default timeout. - - Throws exception if the execution fails. - """ - my_kwargs = {"timeout": TIMEOUT, "shell": False, "simulate": False} - my_kwargs.update(kwargs) - passed, out = execute_arbitrary_command(cmd, **my_kwargs) - if not passed: - raise RuntimeError( - "Failed to execute %s (kwargs=%s), output=%s" % (cmd, kwargs, out)) - -def run_adb_shell_command(cmd: str) -> Tuple[bool, str]: - """Runs command using adb shell. - - Returns: - A tuple of running status (True=succeeded, False=failed or timed out) and - std output (string contents of stdout with trailing whitespace removed). - """ - return run_shell_command('adb shell "{}"'.format(cmd)) - -def run_shell_func(script_path: str, - func: str, - args: List[str]) -> Tuple[bool, str]: - """Runs shell function with default timeout. - - Returns: - A tuple of running status (True=succeeded, False=failed or timed out) and - std output (string contents of stdout with trailing whitespace removed) . - """ - if args: - cmd = 'bash -c "source {script_path}; {func} {args}"'.format( - script_path=script_path, - func=func, - args=' '.join("'{}'".format(arg) for arg in args)) - else: - cmd = 'bash -c "source {script_path}; {func}"'.format( - script_path=script_path, - func=func) - - print_utils.debug_print(cmd) - return run_shell_command(cmd) - -def run_shell_command(cmd: str) -> Tuple[bool, str]: - """Runs shell command with default timeout. - - Returns: - A tuple of running status (True=succeeded, False=failed or timed out) and - std output (string contents of stdout with trailing whitespace removed) . - """ - return execute_arbitrary_command([cmd], - TIMEOUT, - shell=True, - simulate=SIMULATE) - -def execute_arbitrary_command(cmd: List[str], - timeout: int, - shell: bool, - simulate: bool) -> Tuple[bool, str]: - """Run arbitrary shell command with default timeout. - - Mostly copy from - frameworks/base/startop/scripts/app_startup/app_startup_runner.py. - - Args: - cmd: list of cmd strings. - timeout: the time limit of running cmd. - shell: indicate if the cmd is a shell command. - simulate: if it's true, do not run the command and assume the running is - successful. - - Returns: - A tuple of running status (True=succeeded, False=failed or timed out) and - std output (string contents of stdout with trailing whitespace removed) . - """ - if simulate: - print(cmd) - return True, '' - - print_utils.debug_print('[EXECUTE]', cmd) - # block until either command finishes or the timeout occurs. - loop = asyncio.get_event_loop() - - (return_code, script_output) = loop.run_until_complete( - _run_command(*cmd, shell=shell, timeout=timeout)) - - script_output = script_output.decode() # convert bytes to str - - passed = (return_code == 0) - print_utils.debug_print('[$?]', return_code) - if not passed: - print('[FAILED, code:%s]' % (return_code), script_output, file=sys.stderr) - - return passed, script_output.rstrip() - -async def _run_command(*args: List[str], - shell: bool = False, - timeout: Optional[int] = None) -> Tuple[int, bytes]: - if shell: - process = await asyncio.create_subprocess_shell( - *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) - else: - process = await asyncio.create_subprocess_exec( - *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) - - script_output = b'' - - print_utils.debug_print('[PID]', process.pid) - - timeout_remaining = timeout - time_started = time.time() - - # read line (sequence of bytes ending with b'\n') asynchronously - while True: - try: - line = await asyncio.wait_for(process.stdout.readline(), - timeout_remaining) - print_utils.debug_print('[STDOUT]', line) - script_output += line - - if timeout_remaining: - time_elapsed = time.time() - time_started - timeout_remaining = timeout - time_elapsed - except asyncio.TimeoutError: - print_utils.debug_print('[TIMEDOUT] Process ', process.pid) - - print_utils.debug_print('[TIMEDOUT] Sending SIGTERM.') - process.terminate() - - # 5 second timeout for process to handle SIGTERM nicely. - try: - (remaining_stdout, - remaining_stderr) = await asyncio.wait_for(process.communicate(), 5) - script_output += remaining_stdout - except asyncio.TimeoutError: - print_utils.debug_print('[TIMEDOUT] Sending SIGKILL.') - process.kill() - - # 5 second timeout to finish with SIGKILL. - try: - (remaining_stdout, - remaining_stderr) = await asyncio.wait_for(process.communicate(), 5) - script_output += remaining_stdout - except asyncio.TimeoutError: - # give up, this will leave a zombie process. - print_utils.debug_print('[TIMEDOUT] SIGKILL failed for process ', - process.pid) - time.sleep(100) - - return -1, script_output - else: - if not line: # EOF - break - - code = await process.wait() # wait for child process to exit - return code, script_output diff --git a/startop/scripts/lib/logcat_utils.py b/startop/scripts/lib/logcat_utils.py deleted file mode 100644 index 8a3d00b46771..000000000000 --- a/startop/scripts/lib/logcat_utils.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. - -"""Helper util libraries for parsing logcat logs.""" - -import asyncio -import re -from datetime import datetime -from typing import Optional, Pattern - -# local import -import lib.print_utils as print_utils - -def parse_logcat_datetime(timestamp: str) -> Optional[datetime]: - """Parses the timestamp of logcat. - - Params: - timestamp: for example "2019-07-01 16:13:55.221". - - Returns: - a datetime of timestamp with the year now. - """ - try: - # Match the format of logcat. For example: "2019-07-01 16:13:55.221", - # because it doesn't have year, set current year to it. - timestamp = datetime.strptime(timestamp, - '%Y-%m-%d %H:%M:%S.%f') - return timestamp - except ValueError as ve: - print_utils.debug_print('Invalid line: ' + timestamp) - return None - -def _is_time_out(timeout: datetime, line: str) -> bool: - """Checks if the timestamp of this line exceeds the timeout. - - Returns: - true if the timestamp exceeds the timeout. - """ - # Get the timestampe string. - cur_timestamp_str = ' '.join(re.split(r'\s+', line)[0:2]) - timestamp = parse_logcat_datetime(cur_timestamp_str) - if not timestamp: - return False - - return timestamp > timeout - -async def _blocking_wait_for_logcat_pattern(timestamp: datetime, - pattern: Pattern, - timeout: datetime) -> Optional[str]: - # Show the year in the timestampe. - logcat_cmd = 'adb logcat -v UTC -v year -v threadtime -T'.split() - logcat_cmd.append(str(timestamp)) - print_utils.debug_print('[LOGCAT]:' + ' '.join(logcat_cmd)) - - # Create subprocess - process = await asyncio.create_subprocess_exec( - *logcat_cmd, - # stdout must a pipe to be accessible as process.stdout - stdout=asyncio.subprocess.PIPE) - - while (True): - # Read one line of output. - data = await process.stdout.readline() - line = data.decode('utf-8').rstrip() - - # 2019-07-01 14:54:21.946 27365 27392 I ActivityTaskManager: Displayed - # com.android.settings/.Settings: +927ms - # TODO: Detect timeouts even when there is no logcat output. - if _is_time_out(timeout, line): - print_utils.debug_print('DID TIMEOUT BEFORE SEEING ANYTHING (' - 'timeout={timeout} seconds << {pattern} ' - '>>'.format(timeout=timeout, pattern=pattern)) - return None - - if pattern.match(line): - print_utils.debug_print( - 'WE DID SEE PATTERN << "{}" >>.'.format(pattern)) - return line - -def blocking_wait_for_logcat_pattern(timestamp: datetime, - pattern: Pattern, - timeout: datetime) -> Optional[str]: - """Selects the line that matches the pattern and within the timeout. - - Returns: - the line that matches the pattern and within the timeout. - """ - loop = asyncio.get_event_loop() - result = loop.run_until_complete( - _blocking_wait_for_logcat_pattern(timestamp, pattern, timeout)) - return result diff --git a/startop/scripts/lib/logcat_utils_test.py b/startop/scripts/lib/logcat_utils_test.py deleted file mode 100644 index ab82515bc4fa..000000000000 --- a/startop/scripts/lib/logcat_utils_test.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. -# -"""Unit tests for the logcat_utils.py script.""" - -import asyncio -import datetime -import re - -import logcat_utils -from mock import MagicMock, patch - -def test_parse_logcat_datatime(): - # Act - result = logcat_utils.parse_logcat_datetime('2019-07-01 16:13:55.221') - - # Assert - assert result == datetime.datetime(2019, 7, 1, 16, 13, 55, 221000) - -class AsyncMock(MagicMock): - async def __call__(self, *args, **kwargs): - return super(AsyncMock, self).__call__(*args, **kwargs) - -def _async_return(): - f = asyncio.Future() - f.set_result( - b'2019-07-01 15:51:53.290 27365 27392 I ActivityTaskManager: ' - b'Displayed com.google.android.music/com.android.music.activitymanagement.' - b'TopLevelActivity: +1s7ms') - return f - -def test_parse_displayed_time_succeed(): - # Act - with patch('asyncio.create_subprocess_exec', - new_callable=AsyncMock) as asyncio_mock: - asyncio_mock.return_value.stdout.readline = _async_return - timestamp = datetime.datetime(datetime.datetime.now().year, 7, 1, 16, 13, - 55, 221000) - timeout_dt = timestamp + datetime.timedelta(0, 10) - pattern = re.compile('.*ActivityTaskManager: Displayed ' - 'com.google.android.music/com.android.music.*') - result = logcat_utils.blocking_wait_for_logcat_pattern(timestamp, - pattern, - timeout_dt) - - # Assert - assert result == '2019-07-01 15:51:53.290 27365 27392 I ' \ - 'ActivityTaskManager: ' \ - 'Displayed com.google.android.music/com.android.music.' \ - 'activitymanagement.TopLevelActivity: +1s7ms' - -def _async_timeout_return(): - f = asyncio.Future() - f.set_result( - b'2019-07-01 17:51:53.290 27365 27392 I ActivityTaskManager: ' - b'Displayed com.google.android.music/com.android.music.activitymanagement.' - b'TopLevelActivity: +1s7ms') - return f - -def test_parse_displayed_time_timeout(): - # Act - with patch('asyncio.create_subprocess_exec', - new_callable=AsyncMock) as asyncio_mock: - asyncio_mock.return_value.stdout.readline = _async_timeout_return - timestamp = datetime.datetime(datetime.datetime.now().year, - 7, 1, 16, 13, 55, 221000) - timeout_dt = timestamp + datetime.timedelta(0, 10) - pattern = re.compile('.*ActivityTaskManager: Displayed ' - 'com.google.android.music/com.android.music.*') - result = logcat_utils.blocking_wait_for_logcat_pattern(timestamp, - pattern, - timeout_dt) - - # Assert - assert result == None diff --git a/startop/scripts/lib/print_utils.py b/startop/scripts/lib/print_utils.py deleted file mode 100644 index 8c5999d99d6e..000000000000 --- a/startop/scripts/lib/print_utils.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. - -"""Helper util libraries for debug printing.""" - -import sys - -DEBUG = False - -def debug_print(*args, **kwargs): - """Prints the args to sys.stderr if the DEBUG is set.""" - if DEBUG: - print(*args, **kwargs, file=sys.stderr) - -def error_print(*args, **kwargs): - print('[ERROR]:', *args, file=sys.stderr, **kwargs) - -def _expand_gen_repr(args): - """Like repr but any generator-like object has its iterator consumed - and then called repr on.""" - new_args_list = [] - for i in args: - # detect iterable objects that do not have their own override of __str__ - if hasattr(i, '__iter__'): - to_str = getattr(i, '__str__') - if to_str.__objclass__ == object: - # the repr for a generator is just type+address, expand it out instead. - new_args_list.append([_expand_gen_repr([j])[0] for j in i]) - continue - # normal case: uses the built-in to-string - new_args_list.append(i) - return new_args_list - -def debug_print_gen(*args, **kwargs): - """Like _debug_print but will turn any iterable args into a list.""" - if not DEBUG: - return - - new_args_list = _expand_gen_repr(args) - debug_print(*new_args_list, **kwargs) - -def debug_print_nd(*args, **kwargs): - """Like _debug_print but will turn any NamedTuple-type args into a string.""" - if not DEBUG: - return - - new_args_list = [] - for i in args: - if hasattr(i, '_field_types'): - new_args_list.append("%s: %s" % (i.__name__, i._field_types)) - else: - new_args_list.append(i) - - debug_print(*new_args_list, **kwargs) diff --git a/startop/scripts/trace_analyzer/lib/trace2db.py b/startop/scripts/trace_analyzer/lib/trace2db.py deleted file mode 100644 index 42a33aff046d..000000000000 --- a/startop/scripts/trace_analyzer/lib/trace2db.py +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/python3 -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import re -import sys - -from sqlalchemy import create_engine -from sqlalchemy import Column, Date, Integer, Float, String, ForeignKey -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship - -from sqlalchemy.orm import sessionmaker - -import sqlalchemy - -from typing import Optional, Tuple - -_DEBUG = False # print sql commands to console -_FLUSH_LIMIT = 10000 # how many entries are parsed before flushing to DB from memory - -Base = declarative_base() - -class RawFtraceEntry(Base): - __tablename__ = 'raw_ftrace_entries' - - id = Column(Integer, primary_key=True) - task_name = Column(String, nullable=True) # <...> -> None. - task_pid = Column(String, nullable=False) - tgid = Column(Integer, nullable=True) # ----- -> None. - cpu = Column(Integer, nullable=False) - timestamp = Column(Float, nullable=False) - function = Column(String, nullable=False) - function_args = Column(String, nullable=False) - - # 1:1 relation with MmFilemapAddToPageCache. - mm_filemap_add_to_page_cache = relationship("MmFilemapAddToPageCache", - back_populates="raw_ftrace_entry") - - @staticmethod - def parse_dict(line): - # ' <...>-5521 (-----) [003] ...1 17148.446877: tracing_mark_write: trace_event_clock_sync: parent_ts=17148.447266' - m = re.match('\s*(.*)-(\d+)\s+\(([^\)]+)\)\s+\[(\d+)\]\s+([\w.]{4})\s+(\d+[.]\d+):\s+(\w+):\s+(.*)', line) - if not m: - return None - - groups = m.groups() - # groups example: - # ('<...>', - # '5521', - # '-----', - # '003', - # '...1', - # '17148.446877', - # 'tracing_mark_write', - # 'trace_event_clock_sync: parent_ts=17148.447266') - task_name = groups[0] - if task_name == '<...>': - task_name = None - - task_pid = int(groups[1]) - tgid = groups[2] - if tgid == '-----': - tgid = None - - cpu = int(groups[3]) - # irq_flags = groups[4] - timestamp = float(groups[5]) - function = groups[6] - function_args = groups[7] - - return {'task_name': task_name, 'task_pid': task_pid, 'tgid': tgid, 'cpu': cpu, 'timestamp': timestamp, 'function': function, 'function_args': function_args} - -class SchedSwitch(Base): - __tablename__ = 'sched_switches' - - id = Column(Integer, ForeignKey('raw_ftrace_entries.id'), primary_key=True) - - prev_comm = Column(String, nullable=False) - prev_pid = Column(Integer, nullable=False) - prev_prio = Column(Integer, nullable=False) - prev_state = Column(String, nullable=False) - - next_comm = Column(String, nullable=False) - next_pid = Column(Integer, nullable=False) - next_prio = Column(Integer, nullable=False) - - @staticmethod - def parse_dict(function_args, id = None): - # 'prev_comm=kworker/u16:5 prev_pid=13971 prev_prio=120 prev_state=S ==> next_comm=swapper/4 next_pid=0 next_prio=120' - m = re.match("prev_comm=(.*) prev_pid=(\d+) prev_prio=(\d+) prev_state=(.*) ==> next_comm=(.*) next_pid=(\d+) next_prio=(\d+) ?", function_args) - if not m: - return None - - groups = m.groups() - # ('kworker/u16:5', '13971', '120', 'S', 'swapper/4', '0', '120') - d = {} - if id is not None: - d['id'] = id - d['prev_comm'] = groups[0] - d['prev_pid'] = int(groups[1]) - d['prev_prio'] = int(groups[2]) - d['prev_state'] = groups[3] - d['next_comm'] = groups[4] - d['next_pid'] = int(groups[5]) - d['next_prio'] = int(groups[6]) - - return d - -class SchedBlockedReason(Base): - __tablename__ = 'sched_blocked_reasons' - - id = Column(Integer, ForeignKey('raw_ftrace_entries.id'), primary_key=True) - - pid = Column(Integer, nullable=False) - iowait = Column(Integer, nullable=False) - caller = Column(String, nullable=False) - - @staticmethod - def parse_dict(function_args, id = None): - # 'pid=2289 iowait=1 caller=wait_on_page_bit_common+0x2a8/0x5f' - m = re.match("pid=(\d+) iowait=(\d+) caller=(.*) ?", function_args) - if not m: - return None - - groups = m.groups() - # ('2289', '1', 'wait_on_page_bit_common+0x2a8/0x5f8') - d = {} - if id is not None: - d['id'] = id - d['pid'] = int(groups[0]) - d['iowait'] = int(groups[1]) - d['caller'] = groups[2] - - return d - -class MmFilemapAddToPageCache(Base): - __tablename__ = 'mm_filemap_add_to_page_caches' - - id = Column(Integer, ForeignKey('raw_ftrace_entries.id'), primary_key=True) - - dev = Column(Integer, nullable=False) # decoded from ${major}:${minor} syntax. - dev_major = Column(Integer, nullable=False) # original ${major} value. - dev_minor = Column(Integer, nullable=False) # original ${minor} value. - - ino = Column(Integer, nullable=False) # decoded from hex to base 10 - page = Column(Integer, nullable=False) # decoded from hex to base 10 - - pfn = Column(Integer, nullable=False) - ofs = Column(Integer, nullable=False) - - # 1:1 relation with RawFtraceEntry. - raw_ftrace_entry = relationship("RawFtraceEntry", uselist=False) - - @staticmethod - def parse_dict(function_args, id = None): - # dev 253:6 ino b2c7 page=00000000ec787cd9 pfn=1478539 ofs=4096 - m = re.match("dev (\d+):(\d+) ino ([0-9a-fA-F]+) page=([0-9a-fA-F]+) pfn=(\d+) ofs=(\d+)", function_args) - if not m: - return None - - groups = m.groups() - # ('253', '6', 'b2c7', '00000000ec787cd9', '1478539', '4096') - d = {} - if id is not None: - d['id'] = id - - device_major = d['dev_major'] = int(groups[0]) - device_minor = d['dev_minor'] = int(groups[1]) - d['dev'] = device_major << 8 | device_minor - d['ino'] = int(groups[2], 16) - d['page'] = int(groups[3], 16) - d['pfn'] = int(groups[4]) - d['ofs'] = int(groups[5]) - - return d - -class Trace2Db: - def __init__(self, db_filename: str): - (s, e) = self._init_sqlalchemy(db_filename) - self._session = s - self._engine = e - self._raw_ftrace_entry_filter = lambda x: True - - def set_raw_ftrace_entry_filter(self, flt): - """ - Install a function dict(RawFtraceEntry) -> bool - - If this returns 'false', then we skip adding the RawFtraceEntry to the database. - """ - self._raw_ftrace_entry_filter = flt - - @staticmethod - def _init_sqlalchemy(db_filename: str) -> Tuple[object, object]: - global _DEBUG - engine = create_engine('sqlite:///' + db_filename, echo=_DEBUG) - - # CREATE ... (tables) - Base.metadata.create_all(engine) - - Session = sessionmaker(bind=engine) - session = Session() - return (session, engine) - - def parse_file_into_db(self, filename: str, limit: Optional[int] = None): - """ - Parse the ftrace/systrace at 'filename', - inserting the values into the current sqlite database. - - :return: number of RawFtraceEntry inserted. - """ - return parse_file(filename, self._session, self._engine, self._raw_ftrace_entry_filter, limit) - - def parse_file_buf_into_db(self, file_buf, limit: Optional[int] = None): - """ - Parse the ftrace/systrace at 'filename', - inserting the values into the current sqlite database. - - :return: number of RawFtraceEntry inserted. - """ - return parse_file_buf(file_buf, self._session, self._engine, self._raw_ftrace_entry_filter, limit) - - - @property - def session(self): - return self._session - -def insert_pending_entries(engine, kls, lst): - if len(lst) > 0: - # for some reason, it tries to generate an empty INSERT statement with len=0, - # which of course violates the first non-null constraint. - try: - # Performance-sensitive parsing according to: - # https://docs.sqlalchemy.org/en/13/faq/performance.html#i-m-inserting-400-000-rows-with-the-orm-and-it-s-really-slow - engine.execute(kls.__table__.insert(), lst) - lst.clear() - except sqlalchemy.exc.IntegrityError as err: - # possibly violating some SQL constraint, print data here. - print(err) - print(lst) - raise - -def parse_file(filename: str, *args, **kwargs) -> int: - # use explicit encoding to avoid UnicodeDecodeError. - with open(filename, encoding="ISO-8859-1") as f: - return parse_file_buf(f, *args, **kwargs) - -def parse_file_buf(filebuf, session, engine, raw_ftrace_entry_filter, limit=None) -> int: - global _FLUSH_LIMIT - count = 0 - # count and id are not equal, because count still increases for invalid lines. - id = 0 - - pending_entries = [] - pending_sched_switch = [] - pending_sched_blocked_reasons = [] - pending_mm_filemap_add_to_pagecaches = [] - - def insert_all_pending_entries(): - insert_pending_entries(engine, RawFtraceEntry, pending_entries) - insert_pending_entries(engine, SchedSwitch, pending_sched_switch) - insert_pending_entries(engine, SchedBlockedReason, pending_sched_blocked_reasons) - insert_pending_entries(engine, MmFilemapAddToPageCache, pending_mm_filemap_add_to_pagecaches) - - # for trace.html files produced by systrace, - # the actual ftrace is in the 'second' trace-data script class. - parsing_trace_data = 0 - parsing_systrace_file = False - - f = filebuf - for l in f: - if parsing_trace_data == 0 and l == "<!DOCTYPE html>\n": - parsing_systrace_file = True - continue - if parsing_trace_data != 2 and parsing_systrace_file: - if l == ' <script class="trace-data" type="application/text">\n': - parsing_trace_data = parsing_trace_data + 1 - continue - - if parsing_systrace_file and parsing_trace_data != 2: - continue - elif parsing_systrace_file and parsing_trace_data == 2 and l == " </script>\n": - # the rest of this file is just random html - break - - # now parsing the ftrace data. - if len(l) > 1 and l[0] == '#': - continue - - count = count + 1 - - if limit and count >= limit: - break - - raw_ftrace_entry = RawFtraceEntry.parse_dict(l) - if not raw_ftrace_entry: - print("WARNING: Failed to parse raw ftrace entry: " + l) - continue - - if not raw_ftrace_entry_filter(raw_ftrace_entry): - # Skip processing raw ftrace entries that don't match a filter. - # This is an optimization for when Trace2Db is used programatically - # to avoid having an overly large database. - continue - - pending_entries.append(raw_ftrace_entry) - id = id + 1 - - if raw_ftrace_entry['function'] == 'sched_switch': - sched_switch = SchedSwitch.parse_dict(raw_ftrace_entry['function_args'], id) - - if not sched_switch: - print("WARNING: Failed to parse sched_switch: " + l) - else: - pending_sched_switch.append(sched_switch) - - elif raw_ftrace_entry['function'] == 'sched_blocked_reason': - sbr = SchedBlockedReason.parse_dict(raw_ftrace_entry['function_args'], id) - - if not sbr: - print("WARNING: Failed to parse sched_blocked_reason: " + l) - else: - pending_sched_blocked_reasons.append(sbr) - - elif raw_ftrace_entry['function'] == 'mm_filemap_add_to_page_cache': - d = MmFilemapAddToPageCache.parse_dict(raw_ftrace_entry['function_args'], - id) - if not d: - print("WARNING: Failed to parse mm_filemap_add_to_page_cache: " + l) - else: - pending_mm_filemap_add_to_pagecaches.append(d) - - # Objects are cached in python memory, not yet sent to SQL database. - - # Send INSERT/UPDATE/etc statements to the underlying SQL database. - if count % _FLUSH_LIMIT == 0: - insert_all_pending_entries() - - insert_all_pending_entries() - - # Ensure underlying database commits changes from memory to disk. - session.commit() - - return count diff --git a/startop/scripts/trace_analyzer/lib/trace2db_test.py b/startop/scripts/trace_analyzer/lib/trace2db_test.py deleted file mode 100755 index 3b326f000a7d..000000000000 --- a/startop/scripts/trace_analyzer/lib/trace2db_test.py +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. -# - -""" -Unit tests for inode2filename module. - -Install: - $> sudo apt-get install python3-pytest ## OR - $> pip install -U pytest -See also https://docs.pytest.org/en/latest/getting-started.html - -Usage: - $> ./inode2filename_test.py - $> pytest inode2filename_test.py - $> python -m pytest inode2filename_test.py - -See also https://docs.pytest.org/en/latest/usage.html -""" - -# global imports -import io -from copy import deepcopy - -# pip imports -# local imports -from trace2db import * - -# This pretty-prints the raw dictionary of the sqlalchemy object if it fails. -class EqualsSqlAlchemyObject: - # For convenience to write shorter tests, we also add 'ignore_fields' which allow us to specify - # which fields to ignore when doing the comparison. - def __init__(self_, self, ignore_fields=[]): - self_.self = self - self_.ignore_fields = ignore_fields - - # Do field-by-field comparison. - # It seems that SQLAlchemy does not implement __eq__ itself so we have to do it ourselves. - def __eq__(self_, other): - if isinstance(other, EqualsSqlAlchemyObject): - other = other.self - - self = self_.self - - classes_match = isinstance(other, self.__class__) - a, b = deepcopy(self.__dict__), deepcopy(other.__dict__) - - #compare based on equality our attributes, ignoring SQLAlchemy internal stuff - - a.pop('_sa_instance_state', None) - b.pop('_sa_instance_state', None) - - for f in self_.ignore_fields: - a.pop(f, None) - b.pop(f, None) - - attrs_match = (a == b) - return classes_match and attrs_match - - def __repr__(self): - return repr(self.self.__dict__) - - -def assert_eq_ignore_id(left, right): - # This pretty-prints the raw dictionary of the sqlalchemy object if it fails. - # It does field-by-field comparison, but ignores the 'id' field. - assert EqualsSqlAlchemyObject(left, ignore_fields=['id']) == EqualsSqlAlchemyObject(right) - -def parse_trace_file_to_db(*contents): - """ - Make temporary in-memory sqlite3 database by parsing the string contents as a trace. - - :return: Trace2Db instance - """ - buf = io.StringIO() - - for c in contents: - buf.write(c) - buf.write("\n") - - buf.seek(0) - - t2d = Trace2Db(":memory:") - t2d.parse_file_buf_into_db(buf) - - buf.close() - - return t2d - -def test_ftrace_mm_filemap_add_to_pagecache(): - test_contents = """ -MediaStoreImpor-27212 (27176) [000] .... 16136.595194: mm_filemap_add_to_page_cache: dev 253:6 ino 7580 page=0000000060e990c7 pfn=677646 ofs=159744 -MediaStoreImpor-27212 (27176) [000] .... 16136.595920: mm_filemap_add_to_page_cache: dev 253:6 ino 7580 page=0000000048e2e156 pfn=677645 ofs=126976 -MediaStoreImpor-27212 (27176) [000] .... 16136.597793: mm_filemap_add_to_page_cache: dev 253:6 ino 7580 page=0000000051eabfb2 pfn=677644 ofs=122880 -MediaStoreImpor-27212 (27176) [000] .... 16136.597815: mm_filemap_add_to_page_cache: dev 253:6 ino 7580 page=00000000ce7cd606 pfn=677643 ofs=131072 -MediaStoreImpor-27212 (27176) [000] .... 16136.603732: mm_filemap_add_to_page_cache: dev 253:6 ino 1 page=000000008ffd3030 pfn=730119 ofs=186482688 -MediaStoreImpor-27212 (27176) [000] .... 16136.604126: mm_filemap_add_to_page_cache: dev 253:6 ino b1d8 page=0000000098d4d2e2 pfn=829676 ofs=0 - <...>-27197 (-----) [002] .... 16136.613471: mm_filemap_add_to_page_cache: dev 253:6 ino 7580 page=00000000aca88a97 pfn=743346 ofs=241664 - <...>-27197 (-----) [002] .... 16136.615979: mm_filemap_add_to_page_cache: dev 253:6 ino 7580 page=00000000351f2bc1 pfn=777799 ofs=106496 - <...>-27224 (-----) [006] .... 16137.400090: mm_filemap_add_to_page_cache: dev 253:6 ino 712d page=000000006ff7ffdb pfn=754861 ofs=0 - <...>-1396 (-----) [000] .... 16137.451660: mm_filemap_add_to_page_cache: dev 253:6 ino 1 page=00000000ba0cbb34 pfn=769173 ofs=187191296 - <...>-1396 (-----) [000] .... 16137.453020: mm_filemap_add_to_page_cache: dev 253:6 ino b285 page=00000000f6ef038e pfn=820291 ofs=0 - <...>-1396 (-----) [000] .... 16137.453067: mm_filemap_add_to_page_cache: dev 253:6 ino b285 page=0000000083ebc446 pfn=956463 ofs=4096 - <...>-1396 (-----) [000] .... 16137.453101: mm_filemap_add_to_page_cache: dev 253:6 ino b285 page=000000009dc2cd25 pfn=822813 ofs=8192 - <...>-1396 (-----) [000] .... 16137.453113: mm_filemap_add_to_page_cache: dev 253:6 ino b285 page=00000000a11167fb pfn=928650 ofs=12288 - <...>-1396 (-----) [000] .... 16137.453126: mm_filemap_add_to_page_cache: dev 253:6 ino b285 page=00000000c1c3311b pfn=621110 ofs=16384 - <...>-1396 (-----) [000] .... 16137.453139: mm_filemap_add_to_page_cache: dev 253:6 ino b285 page=000000009aa78342 pfn=689370 ofs=20480 - <...>-1396 (-----) [000] .... 16137.453151: mm_filemap_add_to_page_cache: dev 253:6 ino b285 page=0000000082cddcd6 pfn=755584 ofs=24576 - <...>-1396 (-----) [000] .... 16137.453162: mm_filemap_add_to_page_cache: dev 253:6 ino b285 page=00000000b0249bc7 pfn=691431 ofs=28672 - <...>-1396 (-----) [000] .... 16137.453183: mm_filemap_add_to_page_cache: dev 253:6 ino b285 page=000000006a776ff0 pfn=795084 ofs=32768 - <...>-1396 (-----) [000] .... 16137.453203: mm_filemap_add_to_page_cache: dev 253:6 ino b285 page=000000001a4918a7 pfn=806998 ofs=36864 - <...>-2578 (-----) [002] .... 16137.561871: mm_filemap_add_to_page_cache: dev 253:6 ino 1 page=00000000d65af9d2 pfn=719246 ofs=187015168 - <...>-2578 (-----) [002] .... 16137.562846: mm_filemap_add_to_page_cache: dev 253:6 ino b25a page=000000002f6ba74f pfn=864982 ofs=0 - <...>-2578 (-----) [000] .... 16138.104500: mm_filemap_add_to_page_cache: dev 253:6 ino 1 page=00000000f888d0f6 pfn=805812 ofs=192794624 - <...>-2578 (-----) [000] .... 16138.105836: mm_filemap_add_to_page_cache: dev 253:6 ino b7dd page=000000003749523b pfn=977196 ofs=0 - <...>-27215 (-----) [001] .... 16138.256881: mm_filemap_add_to_page_cache: dev 253:6 ino 758f page=000000001b375de1 pfn=755928 ofs=0 - <...>-27215 (-----) [001] .... 16138.257526: mm_filemap_add_to_page_cache: dev 253:6 ino 7591 page=000000004e039481 pfn=841534 ofs=0 - NonUserFacing6-5246 ( 1322) [005] .... 16138.356491: mm_filemap_add_to_page_cache: dev 253:6 ino 1 page=00000000d65af9d2 pfn=719246 ofs=161890304 - NonUserFacing6-5246 ( 1322) [005] .... 16138.357538: mm_filemap_add_to_page_cache: dev 253:6 ino 9a64 page=000000002f6ba74f pfn=864982 ofs=0 - NonUserFacing6-5246 ( 1322) [005] .... 16138.357581: mm_filemap_add_to_page_cache: dev 253:6 ino 9a64 page=000000006e0f8322 pfn=797894 ofs=4096 - <...>-27197 (-----) [005] .... 16140.143224: mm_filemap_add_to_page_cache: dev 253:6 ino 7580 page=00000000a42527c6 pfn=1076669 ofs=32768 - """ - - t2d = parse_trace_file_to_db(test_contents) - session = t2d.session - - first_row = session.query(MmFilemapAddToPageCache).order_by(MmFilemapAddToPageCache.id).first() - - #dev 253:6 ino 7580 page=0000000060e990c7 pfn=677646 ofs=159744 - assert_eq_ignore_id(MmFilemapAddToPageCache(dev=64774, dev_major=253, dev_minor=6, - ino=0x7580, page=0x0000000060e990c7, pfn=677646, ofs=159744), first_row) - - second_to_last_row = session.query(MmFilemapAddToPageCache).filter(MmFilemapAddToPageCache.page.in_([0x000000006e0f8322])).first() - - # dev 253:6 ino 9a64 page=000000006e0f8322 pfn=797894 ofs=4096 - assert_eq_ignore_id(MmFilemapAddToPageCache(dev=64774, dev_major=253, dev_minor=6, - ino=0x9a64, page=0x000000006e0f8322, pfn=797894, ofs=4096), second_to_last_row) - -def test_systrace_mm_filemap_add_to_pagecache(): - test_contents = """ -<!DOCTYPE html> -<html> -<head i18n-values="dir:textdirection;"> -<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> -<meta charset="utf-8"/> -<title>Android System Trace</title> - <script class="trace-data" type="application/text"> -PROCESS DUMP -USER PID PPID VSZ RSS WCHAN PC S NAME COMM -root 1 0 62148 5976 0 0 S init [init] -root 2 0 0 0 0 0 S [kthreadd] [kthreadd] - </script> - - <script class="trace-data" type="application/text"> -MediaStoreImpor-27212 (27176) [000] .... 16136.595194: mm_filemap_add_to_page_cache: dev 253:6 ino 7580 page=0000000060e990c7 pfn=677646 ofs=159744 -NonUserFacing6-5246 ( 1322) [005] .... 16138.357581: mm_filemap_add_to_page_cache: dev 253:6 ino 9a64 page=000000006e0f8322 pfn=797894 ofs=4096 - </script> - - <script class="trace-data" type="application/text"> -{"traceEvents": [{"category": "process_argv", "name": "process_argv", "args": {"argv": ["/mnt/ssd3/workspace/master/external/chromium-trace/systrace.py", "-t", "5", "pagecache"]}, "pid": 160383, "ts": 1037300940509.7991, "tid": 139628672526080, "ph": "M"}, {"category": "python", "name": "clock_sync", "args": {"issue_ts": 1037307346185.212, "sync_id": "9a7e4fe3-89ad-441f-8226-8fe533fe973e"}, "pid": 160383, "ts": 1037307351643.906, "tid": 139628726089536, "ph": "c"}], "metadata": {"clock-domain": "SYSTRACE"}} - </script> -<!-- END TRACE --> - """ - - t2d = parse_trace_file_to_db(test_contents) - session = t2d.session - - first_row = session.query(MmFilemapAddToPageCache).order_by(MmFilemapAddToPageCache.id).first() - - #dev 253:6 ino 7580 page=0000000060e990c7 pfn=677646 ofs=159744 - assert_eq_ignore_id(MmFilemapAddToPageCache(dev=64774, dev_major=253, dev_minor=6, - ino=0x7580, page=0x0000000060e990c7, pfn=677646, ofs=159744), first_row) - - second_to_last_row = session.query(MmFilemapAddToPageCache).filter(MmFilemapAddToPageCache.page.in_([0x000000006e0f8322])).first() - - # dev 253:6 ino 9a64 page=000000006e0f8322 pfn=797894 ofs=4096 - assert_eq_ignore_id(MmFilemapAddToPageCache(dev=64774, dev_major=253, dev_minor=6, - ino=0x9a64, page=0x000000006e0f8322, pfn=797894, ofs=4096), second_to_last_row) - -def test_timestamp_filter(): - test_contents = """ - MediaStoreImpor-27212 (27176) [000] .... 16136.595194: mm_filemap_add_to_page_cache: dev 253:6 ino 7580 page=0000000060e990c7 pfn=677646 ofs=159744 - NonUserFacing6-5246 ( 1322) [005] .... 16139.357581: mm_filemap_add_to_page_cache: dev 253:6 ino 9a64 page=000000006e0f8322 pfn=797894 ofs=4096 - MediaStoreImpor-27212 (27176) [000] .... 16136.604126: mm_filemap_add_to_page_cache: dev 253:6 ino b1d8 page=0000000098d4d2e2 pfn=829676 ofs=0 - """ - - t2d = parse_trace_file_to_db(test_contents) - session = t2d.session - - end_time = 16137.0 - - results = session.query(MmFilemapAddToPageCache).join( - MmFilemapAddToPageCache.raw_ftrace_entry).filter( - RawFtraceEntry.timestamp <= end_time).order_by( - MmFilemapAddToPageCache.id).all() - - assert len(results) == 2 - assert_eq_ignore_id( - MmFilemapAddToPageCache(dev=64774, dev_major=253, dev_minor=6, - ino=0x7580, page=0x0000000060e990c7, pfn=677646, - ofs=159744), results[0]) - assert_eq_ignore_id( - MmFilemapAddToPageCache(dev=64774, dev_major=253, dev_minor=6, - ino=0xb1d8, page=0x0000000098d4d2e2, pfn=829676, - ofs=0), results[1]) - - -if __name__ == '__main__': - pytest.main() diff --git a/startop/scripts/trace_analyzer/queries_all.sql b/startop/scripts/trace_analyzer/queries_all.sql deleted file mode 100644 index 41d1c0804a7f..000000000000 --- a/startop/scripts/trace_analyzer/queries_all.sql +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --- filter for atrace writes -CREATE VIEW IF NOT EXISTS tracing_mark_writes AS - SELECT * - FROM raw_ftrace_entries - WHERE function = 'tracing_mark_write'; - --- split the tracing_mark_write function args by ||s -DROP TABLE IF exists tracing_mark_write_split_array; - -CREATE TABLE tracing_mark_write_split_array ( - predictorset_id INT REFERENCES raw_ftrace_entries (id), - predictor_name, - rest, - gen, - - UNIQUE(predictorset_id, gen) -- drops redundant inserts into table -); - -CREATE INDEX "tracing_mark_write_split_array_id" ON tracing_mark_write_split_array ( - predictorset_id COLLATE BINARY COLLATE BINARY -); - -INSERT INTO tracing_mark_write_split_array - WITH - split(predictorset_id, predictor_name, rest, gen) AS ( - -- split by | - SELECT id, '', function_args || '|', 0 FROM tracing_mark_writes WHERE id - UNION ALL - SELECT predictorset_id, - substr(rest, 0, instr(rest, '|')), - substr(rest, instr(rest, '|')+1), - gen + 1 - FROM split - WHERE rest <> ''), - split_results AS ( - SELECT * FROM split WHERE predictor_name <> '' - ) - SELECT * from split_results -; - - diff --git a/startop/scripts/trace_analyzer/queries_app_launch_spans_with_name.sql b/startop/scripts/trace_analyzer/queries_app_launch_spans_with_name.sql deleted file mode 100644 index c28475eec73d..000000000000 --- a/startop/scripts/trace_analyzer/queries_app_launch_spans_with_name.sql +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --- use the 'launching: $process_name' async slice to figure out launch duration. -DROP VIEW IF EXISTS launch_durations_named; - -CREATE VIEW launch_durations_named AS -WITH - launch_traces_raw AS ( - SELECT * - FROM tracing_mark_write_split AS tmw, - raw_ftrace_entries AS rfe - WHERE atrace_message LIKE 'launching: %' AND rfe.id = tmw.raw_ftrace_entry_id - ), - launch_traces_joined AS ( - SELECT started.timestamp AS started_timestamp, - finished.timestamp AS finished_timestamp, - started.id AS started_id, - finished.id AS finished_id, - SUBSTR(started.atrace_message, 12) AS proc_name -- crop out "launching: " from the string. - FROM launch_traces_raw AS started, - launch_traces_raw AS finished - -- async slices ('S' -> 'F') have matching counters given the same PID. - WHERE started.atrace_type == 'S' - AND finished.atrace_type == 'F' - AND started.atrace_count == finished.atrace_count - AND started.atrace_pid == finished.atrace_pid - ) -SELECT * from launch_traces_joined; - -SELECT * FROM launch_durations_named; diff --git a/startop/scripts/trace_analyzer/queries_block_launch.sql b/startop/scripts/trace_analyzer/queries_block_launch.sql deleted file mode 100644 index 34e5f03d0c48..000000000000 --- a/startop/scripts/trace_analyzer/queries_block_launch.sql +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -DROP VIEW IF EXISTS blocked_iowait_for_app_launches; - -CREATE VIEW blocked_iowait_for_app_launches AS -WITH - block_launch_join AS ( - SELECT * - FROM blocking_durations AS bd, - launch_durations_named AS ld - WHERE bd.block_timestamp >= ld.started_timestamp - AND bd.unblock_timestamp <= ld.finished_timestamp - ), - blocked_ui_threads AS ( - SELECT * - FROM start_process_ui_threads AS sp, - block_launch_join AS blj - WHERE sp.atm_ui_thread_tid == unblock_pid - AND sp.process_name = blj.proc_name - ), - summed_raw AS ( - SELECT SUM(unblock_timestamp-block_timestamp)*1000 AS sum_block_duration_ms, - * - FROM blocked_ui_threads - GROUP BY unblock_pid - ), - summed_neat AS ( - SELECT sum_block_duration_ms AS blocked_iowait_duration_ms, - process_name, - (finished_timestamp - started_timestamp) * 1000 AS launching_duration_ms, - started_timestamp * 1000 AS launching_started_timestamp_ms, - finished_timestamp * 1000 AS launching_finished_timestamp_ms - -- filter out the rest because its just selecting 1 arbitrary row (due to the SUM aggregate)., - FROM summed_raw - ) -SELECT * FROM summed_neat; - -SELECT * FROM blocked_iowait_for_app_launches; diff --git a/startop/scripts/trace_analyzer/queries_find_sched_switch_unblocked.sql b/startop/scripts/trace_analyzer/queries_find_sched_switch_unblocked.sql deleted file mode 100644 index 788d0dae47d3..000000000000 --- a/startop/scripts/trace_analyzer/queries_find_sched_switch_unblocked.sql +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -DROP VIEW IF EXISTS sched_switch_iowaits_pre; - --- scan for the closest pair such that: --- sched_block_reason pid=$PID iowait=1 ... --- ... --- sched_switch next_pid=$PID -CREATE VIEW sched_switch_iowaits_pre AS - SELECT MAX(sbr.id) AS blocked_id, - ss.id AS sched_switch_id, - pid, -- iow.pid - iowait, -- iowait=0 or iowait=1 - caller, - sbr_f.timestamp AS blocked_timestamp, - ss_f.timestamp AS sched_switch_timestamp, - next_comm, -- name of next_pid - next_pid -- same as iow.pid - FROM sched_blocked_reasons AS sbr, - raw_ftrace_entries AS sbr_f, - sched_switches AS ss, - raw_ftrace_entries AS ss_f - WHERE sbr_f.id == sbr.id - AND ss_f.id == ss.id - AND sbr.pid == ss.next_pid - AND sbr.iowait = 1 - AND sbr_f.timestamp < ss_f.timestamp -- ensures the 'closest' sched_blocked_reason is selected. - GROUP BY ss.id -; - -DROP VIEW IF EXISTS sched_switch_iowaits; - -CREATE VIEW sched_switch_iowaits AS - SELECT *, MIN(sched_switch_timestamp) AS ss_timestamp -- drop all of the 'too large' sched_switch entries except the closest one. - FROM sched_switch_iowaits_pre - GROUP BY blocked_id; - -SELECT * FROM sched_switch_iowaits; - --- use a real table here instead of a view, otherwise SQLiteStudio segfaults for some reason. -DROP TABLE IF EXISTS blocking_durations; - -CREATE TABLE blocking_durations AS -WITH - blocking_durations_raw AS ( - SELECT MAX(ss.id) AS block_id, - ssf.timestamp AS block_timestamp, - iow.sched_switch_timestamp AS unblock_timestamp, - ss.prev_comm as block_prev_comm, - iow.next_comm AS unblock_next_comm, - ss.prev_state AS block_prev_state, - iow.sched_switch_id AS unblock_id, - iow.pid AS unblock_pid, - iow.caller AS unblock_caller - FROM sched_switches AS ss, -- this is the sched_switch that caused a block (in the future when it unblocks, the reason is iowait=1). - sched_switch_iowaits AS iow, -- this is the sched_switch that removes the block (it is now running again). - raw_ftrace_entries AS ssf - WHERE ssf.id = ss.id AND ss.prev_pid == iow.next_pid AND ssf.timestamp < iow.sched_switch_timestamp - GROUP BY unblock_timestamp - ), - blocking_durations_tmp AS ( - SELECT block_id, - unblock_timestamp, - block_timestamp, - block_prev_comm as comm, - block_prev_state as block_state, - unblock_id, - unblock_pid, - unblock_caller - FROM blocking_durations_raw - ) - SELECT * FROM blocking_durations_tmp;-- ORDER BY block_id ASC; - --SELECT SUM(block_duration_ms) AS sum, * FROM blocking_durations GROUP BY unblock_pid ORDER BY sum DESC; - -DROP INDEX IF EXISTS "blocking_durations_block_timestamp"; - -CREATE INDEX "blocking_durations_block_timestamp" ON blocking_durations ( - block_timestamp COLLATE BINARY COLLATE BINARY -); - -DROP INDEX IF EXISTS "blocking_durations_unblock_timestamp"; - -CREATE INDEX "blocking_durations_unblock_timestamp" ON blocking_durations ( - unblock_timestamp COLLATE BINARY COLLATE BINARY -); - -SELECT * FROM blocking_durations; diff --git a/startop/scripts/trace_analyzer/queries_get_comm_and_pids.sql b/startop/scripts/trace_analyzer/queries_get_comm_and_pids.sql deleted file mode 100644 index 0c166b09784c..000000000000 --- a/startop/scripts/trace_analyzer/queries_get_comm_and_pids.sql +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -DROP VIEW IF EXISTS sched_switch_next_comm_pids; - -CREATE VIEW IF NOT EXISTS sched_switch_next_comm_pids AS - --- TODO: switch to using sched_switches table. - -WITH - sched_switchs AS ( - SELECT * FROM raw_ftrace_entries WHERE function = 'sched_switch' AND function_args LIKE '% next_pid=%' AND function_args NOT LIKE '% next_comm=main %' - ), - comm_and_pids_raws AS ( - SELECT id, - SUBSTR(function_args, instr(function_args, "next_comm="), instr(function_args, "next_pid=") - instr(function_args, "next_comm=")) AS next_comm_raw, - SUBSTR(function_args, instr(function_args, "next_pid="), instr(function_args, "next_prio=") - instr(function_args, "next_pid=")) AS next_pid_raw - FROM sched_switchs - ), - comm_and_pids AS ( - SELECT id, - id AS raw_ftrace_entry_id, - TRIM(SUBSTR(next_pid_raw, 10)) AS next_pid, -- len("next_pid=") is 10 - TRIM(SUBSTR(next_comm_raw, 11)) AS next_comm -- len("next_comm=") is 11 - FROM comm_and_pids_raws - ) -SELECT * from comm_and_pids; - -SELECT * from sched_switch_next_comm_pids; diff --git a/startop/scripts/trace_analyzer/queries_get_procs.sql b/startop/scripts/trace_analyzer/queries_get_procs.sql deleted file mode 100644 index 06871c6e16b6..000000000000 --- a/startop/scripts/trace_analyzer/queries_get_procs.sql +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -DROP VIEW IF EXISTS start_procs; - -CREATE VIEW IF NOT EXISTS start_procs AS -WITH - start_procs_raw AS ( - SELECT * from tracing_mark_write_split WHERE atrace_message LIKE 'Start proc: %' - ), - start_procs_substr AS ( - -- note: "12" is len("Start proc: ")+1. sqlite indices start at 1. - SELECT raw_ftrace_entry_id, atrace_pid, SUBSTR(atrace_message, 13) AS process_name FROM start_procs_raw - ) -SELECT * from start_procs_substr; - -SELECT * from start_procs; diff --git a/startop/scripts/trace_analyzer/queries_get_ui_threads.sql b/startop/scripts/trace_analyzer/queries_get_ui_threads.sql deleted file mode 100644 index 876e50e9880a..000000000000 --- a/startop/scripts/trace_analyzer/queries_get_ui_threads.sql +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - --- note: These queries do comparisons based on raw_ftrace_entries.id by treating it as if it was equivalent to the temporal timestamp. --- in practice, the ID of raw_ftrace_entries is based on its order in the ftrace buffer [and on the same cpu its equivalent]. --- we can always resort raw_ftrace_entries to ensure id order matches timestamp order. We should rarely need to compare by timestamp directly. --- accessing 'floats' is inferior as they are harder to index, and will result in slower queries. --- --- Naming convention note: '_fid' corresponds to 'raw_ftrace_entry.id'. -DROP VIEW IF EXISTS start_process_ui_threads; - --- Map of started process names to their UI thread's TID (as returned by gettid). -CREATE VIEW IF NOT EXISTS start_process_ui_threads AS -WITH - start_proc_tids AS ( - SELECT sp.raw_ftrace_entry_id AS start_proc_fid, - sp.atrace_pid AS atrace_pid, - sp.process_name AS process_name, - --MIN(nc.raw_ftrace_entry_id) as next_comm_fid, - nc.raw_ftrace_entry_id AS next_comm_fid, - nc.next_pid as next_pid, - nc.next_comm as next_comm, - SUBSTR(sp.process_name, -15) AS cut -- why -15? See TASK_MAX in kernel, the sched_switch name is truncated to 16 bytes. - FROM start_procs AS sp, - sched_switch_next_comm_pids AS nc - WHERE sp.process_name LIKE '%' || nc.next_comm -- kernel truncates the sched_switch::next_comm event, so we must match the prefix of the full name. - --WHERE SUBSTR(sp.process_name, -16) == nc.next_comm - --WHERE cut == nc.next_comm - ), - start_proc_tids_filtered AS ( - SELECT * - FROM start_proc_tids - WHERE next_comm_fid > start_proc_fid -- safeguard that avoids choosing "earlier" sched_switch before process was even started. - --ORDER BY start_proc_fid, next_comm_fid - ), - start_proc_all_threads AS ( - SELECT DISTINCT - start_proc_fid, -- this is the ftrace entry of the system server 'Start proc: $process_name'. only need this to join for timestamp. - process_name, -- this is the '$process_name' from the system server entry. - -- next up we have all the possible thread IDs as parsed from sched_switch that corresponds most closest to the start proc. - next_pid AS ui_thread_tpid, -- sched_switch.next_pid. This can be any of the threads in that process, it's not necessarily the main UI thread yet. - next_comm, - MIN(next_comm_fid) AS next_comm_fid -- don't pick the 'later' next_comm_fid because it could correspond to another app start. - FROM start_proc_tids_filtered - GROUP BY start_proc_fid, ui_thread_tpid - ), - activity_thread_mains AS ( - SELECT * FROM tracing_mark_write_split WHERE atrace_message = 'ActivityThreadMain' - ), - start_proc_ui_threads AS ( - SELECT start_proc_fid, - process_name, - ui_thread_tpid, - next_comm, - next_comm_fid, - atm.raw_ftrace_entry_id as atm_fid, - atm.atrace_pid as atm_ui_thread_tid - FROM start_proc_all_threads AS spt, - activity_thread_mains AS atm - WHERE atm.atrace_pid == spt.ui_thread_tpid AND atm.raw_ftrace_entry_id > spt.start_proc_fid -- Ensure we ignore earlier ActivityThreadMains prior to their Start proc. - ), - start_proc_ui_threads_filtered AS ( - SELECT start_proc_fid, - process_name, -- e.g. 'com.android.settings' - --ui_thread_tpid, - --next_comm, - --next_comm_fid, - MIN(atm_fid) AS atm_fid, - atm_ui_thread_tid -- equivalent to gettid() for the process's UI thread. - FROM start_proc_ui_threads - GROUP BY start_proc_fid, atm_ui_thread_tid -- find the temporally closest ActivityTaskMain to a "Start proc: $process_name" - ) -SELECT * FROM start_proc_ui_threads_filtered; - -SELECT * FROM start_process_ui_threads; diff --git a/startop/scripts/trace_analyzer/queries_mark_write_join.sql b/startop/scripts/trace_analyzer/queries_mark_write_join.sql deleted file mode 100644 index 100f07403423..000000000000 --- a/startop/scripts/trace_analyzer/queries_mark_write_join.sql +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -DROP TABLE IF EXISTS tracing_mark_write_split; - -CREATE TABLE tracing_mark_write_split ( - raw_ftrace_entry_id INT REFERENCES raw_ftrace_entries (id), - atrace_type CHAR(1), -- only null for the first 2 sync timers. usually 'B', 'C', E', ... - atrace_pid INT, -- only null for first 2 sync timers - atrace_message, -- usually null for type='E' etc. - atrace_count, -- usually non-null only for 'C' - - UNIQUE(raw_ftrace_entry_id) -- drops redundant inserts into table -); - -INSERT INTO tracing_mark_write_split -WITH - pivoted AS ( - SELECT tx.predictorset_id, - --ty.predictorset_id, - --tz.predictorset_id, - --tzz.predictorset_id, - tx.predictor_name AS atrace_type, - CAST(ty.predictor_name AS integer) AS atrace_pid, - tz.predictor_name AS atrace_message, - CAST(tzz.predictor_name AS integer) AS atrace_count - FROM (SELECT * from tracing_mark_write_split_array WHERE gen = 1) AS tx - LEFT JOIN - (SELECT * FROM tracing_mark_write_split_array WHERE gen = 2) AS ty - ON tx.predictorset_id = ty.predictorset_id - LEFT JOIN - (SELECT * FROM tracing_mark_write_split_array WHERE gen = 3) AS tz - ON tx.predictorset_id = tz.predictorset_id - LEFT JOIN - (SELECT * FROM tracing_mark_write_split_array WHERE gen = 4) AS tzz - ON tx.predictorset_id = tzz.predictorset_id - ) -SELECT * from pivoted ORDER BY predictorset_id;-- LIMIT 100; - -SELECT * FROM tracing_mark_write_split; diff --git a/startop/scripts/trace_analyzer/run-sql-queries b/startop/scripts/trace_analyzer/run-sql-queries deleted file mode 100755 index 61a0ad42a339..000000000000 --- a/startop/scripts/trace_analyzer/run-sql-queries +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -DIR="$( cd "$(dirname "$0")" ; pwd -P )" - -if [[ $# -lt 1 ]]; then - echo "Usage: $0 <db-file>" -fi - -DB_TARGET=$1 - -if ! [[ -f $DB_TARGET ]]; then - echo "ERROR: File '$DB_TARGET' does not exist." >&2 - exit 1 -fi - -exec_sql_file() { - local filename="$1" - if ! [[ -f $filename ]]; then - echo "ERROR: Can't exec SQL file, '$filename' does not exist." >&2 - return 1 - fi - - sqlite3 "$DB_TARGET" < "$DIR"/"$filename" -} - -exec_sql_file_quiet() { - exec_sql_file "$@" > /dev/null -} - -# Some views/tables need other views already created, so order does matter. -# x -> y , means x depends on y. - -# View: tracing_mark_writes -# Table: tracing_mark_write_split_array -> tracing_mark_writes -exec_sql_file_quiet "queries_all.sql" - -# Table: tracing_mark_write_split -> tracing_mark_write_split_array -exec_sql_file_quiet "queries_mark_write_join.sql" - -# View: start_procs -> tracing_mark_write_split -exec_sql_file_quiet "queries_get_procs.sql" - -# View: sched_switch_next_comm_pids -exec_sql_file_quiet "queries_get_comm_and_pids.sql" - -# View: start_process_ui_threads -> start_procs, sched_switch_next_comm_pids -exec_sql_file_quiet "queries_get_ui_threads.sql" - -# View: launch_durations_named -> tracing_mark_write_split -exec_sql_file_quiet "queries_app_launch_spans_with_name.sql" - -# View: sched_switch_iowaits_pre -# View: sched_switch_iowaits -> sched_switch_iowaits_pre -# Table: blocking_durations -> sched_switch_iowaits -exec_sql_file_quiet "queries_find_sched_switch_unblocked.sql" - -# View: blocked_iowait_for_app_launches -> launch_durations_named, blocking_durations -exec_sql_file_quiet "queries_block_launch.sql" - -##### -##### -##### - -# Final queries - -exec_sql_file "queries_pretty_print_block_launch.sql" diff --git a/startop/scripts/trace_analyzer/test_fixtures/common_systrace b/startop/scripts/trace_analyzer/test_fixtures/common_systrace deleted file mode 100644 index 802cb5562593..000000000000 --- a/startop/scripts/trace_analyzer/test_fixtures/common_systrace +++ /dev/null @@ -1,518 +0,0 @@ -# tracer: nop -# -# entries-in-buffer/entries-written: 411983/411983 #P:8 -# -# _-----=> irqs-off -# / _----=> need-resched -# | / _---=> hardirq/softirq -# || / _--=> preempt-depth -# ||| / delay -# TASK-PID TGID CPU# |||| TIMESTAMP FUNCTION -# | | | | |||| | | - <...>-14603 (-----) [000] ...1 14592.893157: tracing_mark_write: trace_event_clock_sync: parent_ts=14592.892578 - <...>-14603 (-----) [000] ...1 14592.893172: tracing_mark_write: trace_event_clock_sync: realtime_ts=1557129597951 - <...>-18150 (-----) [004] d..2 14594.182110: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=D ==> next_comm=kworker/u16:16 next_pid=23269 next_prio=120 - kworker/u16:16-23269 (23269) [004] d.h3 14594.182228: sched_blocked_reason: pid=18150 iowait=0 caller=a6xx_oob_set+0x194/0x3dc - kworker/u16:16-23269 (23269) [004] d..2 14594.182248: sched_switch: prev_comm=kworker/u16:16 prev_pid=23269 prev_prio=120 prev_state=D ==> next_comm=kworker/u16:18 next_pid=18150 next_prio=120 - <...>-18150 (-----) [004] d..2 14594.182312: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=D ==> next_comm=swapper/4 next_pid=0 next_prio=120 - <...>-18150 (-----) [004] d..2 14594.182488: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=D ==> next_comm=swapper/4 next_pid=0 next_prio=120 - kworker/u16:16-23269 (23269) [005] d..2 14594.182610: sched_switch: prev_comm=kworker/u16:16 prev_pid=23269 prev_prio=120 prev_state=S ==> next_comm=swapper/5 next_pid=0 next_prio=120 - <...>-18150 (-----) [004] d..2 14594.182626: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=D ==> next_comm=swapper/4 next_pid=0 next_prio=120 - <...>-18150 (-----) [004] d..2 14594.182755: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=D ==> next_comm=swapper/4 next_pid=0 next_prio=120 - <...>-18150 (-----) [004] d..2 14594.182975: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=D ==> next_comm=swapper/4 next_pid=0 next_prio=120 - <...>-18150 (-----) [004] d..2 14594.183209: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=D ==> next_comm=swapper/4 next_pid=0 next_prio=120 - <...>-18150 (-----) [004] d..2 14594.183371: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=D ==> next_comm=swapper/4 next_pid=0 next_prio=120 - <...>-18150 (-----) [004] d..2 14594.184286: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=S ==> next_comm=swapper/4 next_pid=0 next_prio=120 - kworker/u16:16-23269 (23269) [005] d..2 14594.184495: sched_switch: prev_comm=kworker/u16:16 prev_pid=23269 prev_prio=120 prev_state=S ==> next_comm=swapper/5 next_pid=0 next_prio=120 - <...>-18150 (-----) [004] d..2 14594.184498: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=S ==> next_comm=swapper/4 next_pid=0 next_prio=120 - ksoftirqd/4-47 ( 47) [004] d..2 14594.185678: sched_switch: prev_comm=ksoftirqd/4 prev_pid=47 prev_prio=120 prev_state=S ==> next_comm=swapper/4 next_pid=0 next_prio=120 - kworker/6:2-10610 (10610) [006] d..2 14594.186012: sched_switch: prev_comm=kworker/6:2 prev_pid=10610 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120 - <...>-656 (-----) [001] .... 14594.219464: binder_set_priority: proc=625 thread=656 old=110 => new=120 desired=120 - <...>-1803 (-----) [000] d..2 14594.219595: sched_switch: prev_comm=ndroid.systemui prev_pid=1803 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120 - <...>-3359 (-----) [001] ...1 14594.219856: tracing_mark_write: S|1368|launching: com.google.android.dialer|0 - <...>-3359 (-----) [001] ...1 14594.219863: tracing_mark_write: B|1368|MetricsLogger:launchObserverNotifyActivityLaunched - <...>-3359 (-----) [001] ...1 14594.219869: tracing_mark_write: B|1368|MetricsLogger:convertActivityRecordToProto - <...>-1398 (-----) [006] ...1 14594.220160: tracing_mark_write: B|1368|updateInputWindows - <...>-3359 (-----) [001] .... 14594.220230: binder_set_priority: proc=1368 thread=3359 old=110 => new=120 desired=120 - <...>-1398 (-----) [006] ...1 14594.220588: tracing_mark_write: B|1368|android.os.Handler: com.android.server.wm.AppWindowToken$1 - <...>-1398 (-----) [006] ...1 14594.220722: tracing_mark_write: B|1368|ResourcesManager#getResources - <...>-1052 (-----) [002] d..2 14594.220884: sched_switch: prev_comm=statsd.writer prev_pid=1052 prev_prio=120 prev_state=S ==> next_comm=UiThreadHelper next_pid=2045 next_prio=118 - <...>-1398 (-----) [006] ...1 14594.220926: tracing_mark_write: B|1368|Theme::ApplyStyle - <...>-1398 (-----) [006] ...1 14594.220929: tracing_mark_write: B|1368|AssetManager::GetBag - <...>-2007 (-----) [007] ...1 14594.220996: tracing_mark_write: B|2007|Choreographer#doFrame - <...>-2007 (-----) [007] ...1 14594.221005: tracing_mark_write: B|2007|animation - <...>-1398 (-----) [006] ...1 14594.221015: tracing_mark_write: B|1368|ResourcesManager#getResources - <...>-2045 (-----) [002] ...2 14594.221035: binder_set_priority: proc=1368 thread=1903 old=120 => new=118 desired=118 - <...>-2045 (-----) [002] d..2 14594.221065: sched_switch: prev_comm=UiThreadHelper prev_pid=2045 prev_prio=118 prev_state=S ==> next_comm=Binder:1368_4 next_pid=1903 next_prio=118 - <...>-1398 (-----) [006] ...1 14594.221080: tracing_mark_write: B|1368|AssetManager::SetApkAssets - <...>-2007 (-----) [007] ...1 14594.221110: tracing_mark_write: B|2007|traversal - <...>-656 (-----) [000] ...1 14594.221137: tracing_mark_write: B|625|requestNextVsync - <...>-656 (-----) [000] ...1 14594.221141: tracing_mark_write: B|625|resetIdleTimer - <...>-2007 (-----) [007] ...1 14594.221146: tracing_mark_write: B|2007|draw - <...>-2007 (-----) [007] ...1 14594.221160: tracing_mark_write: B|2007|Record View#draw() - <...>-660 (-----) [005] d..2 14594.221285: sched_switch: prev_comm=app prev_pid=660 prev_prio=97 prev_state=S ==> next_comm=RenderThread next_pid=2738 next_prio=110 - <...>-658 (-----) [004] d..2 14594.221327: sched_switch: prev_comm=DispSync prev_pid=658 prev_prio=97 prev_state=S ==> next_comm=android.display next_pid=1397 next_prio=117 - <...>-2738 (-----) [005] ...1 14594.221342: tracing_mark_write: B|2007|notifyFramePending - <...>-2738 (-----) [005] ...1 14594.221362: tracing_mark_write: B|2007|DrawFrame - <...>-2738 (-----) [005] ...1 14594.221369: tracing_mark_write: B|2007|query - <...>-2007 (-----) [007] d..2 14594.221369: sched_switch: prev_comm=s.nexuslauncher prev_pid=2007 prev_prio=110 prev_state=S ==> next_comm=swapper/7 next_pid=0 next_prio=120 - <...>-1903 (-----) [002] .... 14594.221397: binder_set_priority: proc=1368 thread=1903 old=118 => new=120 desired=120 - <...>-2738 (-----) [005] ...2 14594.221400: binder_set_priority: proc=625 thread=656 old=120 => new=110 desired=110 - <...>-2738 (-----) [005] d..2 14594.221430: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=110 - <...>-1368 (-----) [003] ...1 14594.221431: tracing_mark_write: B|1368|Lock contention on GC thread flip lock (owner tid: 0) - <...>-656 (-----) [005] ...1 14594.221460: tracing_mark_write: B|625|query - <...>-656 (-----) [005] .... 14594.221528: binder_set_priority: proc=625 thread=656 old=110 => new=120 desired=120 - <...>-2738 (-----) [007] ...1 14594.221552: tracing_mark_write: B|2007|query - <...>-2738 (-----) [007] ...2 14594.221563: binder_set_priority: proc=625 thread=656 old=120 => new=110 desired=110 - <...>-2738 (-----) [007] d..2 14594.221600: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=110 - <...>-1368 (-----) [003] d..2 14594.221623: sched_switch: prev_comm=system_server prev_pid=1368 prev_prio=118 prev_state=S ==> next_comm=swapper/3 next_pid=0 next_prio=120 - <...>-656 (-----) [007] ...1 14594.221628: tracing_mark_write: B|625|query - <...>-23031 (-----) [001] d..2 14594.221643: sched_switch: prev_comm=UiAutomation prev_pid=23031 prev_prio=120 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-2738 (-----) [007] ...1 14594.221664: tracing_mark_write: B|2007|syncFrameState - <...>-2738 (-----) [007] ...1 14594.221697: tracing_mark_write: B|2007|prepareTree - <...>-23008 (-----) [005] d..2 14594.221706: sched_switch: prev_comm=hub.uiautomator prev_pid=23008 prev_prio=120 prev_state=S ==> next_comm=swapper/5 next_pid=0 next_prio=120 - <...>-656 (-----) [000] .... 14594.221737: binder_set_priority: proc=625 thread=656 old=110 => new=120 desired=120 - <...>-1803 (-----) [003] d..2 14594.221747: sched_switch: prev_comm=ndroid.systemui prev_pid=1803 prev_prio=120 prev_state=S ==> next_comm=swapper/3 next_pid=0 next_prio=120 - <...>-1397 (-----) [004] d..2 14594.221806: sched_switch: prev_comm=android.display prev_pid=1397 prev_prio=117 prev_state=S ==> next_comm=Binder:2007_A next_pid=4180 next_prio=120 - <...>-1398 (-----) [006] d..2 14594.221816: sched_switch: prev_comm=android.anim prev_pid=1398 prev_prio=110 prev_state=R ==> next_comm=s.nexuslauncher next_pid=2007 next_prio=110 - <...>-2738 (-----) [007] ...1 14594.221824: tracing_mark_write: B|2007|query - <...>-2738 (-----) [007] ...1 14594.221830: tracing_mark_write: B|2007|query - <...>-2738 (-----) [007] ...1 14594.221834: tracing_mark_write: B|2007|query - <...>-2738 (-----) [007] ...1 14594.221841: tracing_mark_write: B|2007|query - <...>-2738 (-----) [007] ...1 14594.221843: tracing_mark_write: B|2007|query - <...>-2738 (-----) [007] ...1 14594.221846: tracing_mark_write: B|2007|query - <...>-2738 (-----) [007] ...1 14594.221850: tracing_mark_write: B|2007|dequeueBuffer - <...>-2738 (-----) [007] ...2 14594.221864: binder_set_priority: proc=625 thread=656 old=120 => new=110 desired=110 - <...>-2738 (-----) [007] d..2 14594.221985: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=R+ ==> next_comm=crtc_event:97 next_pid=303 next_prio=83 - <...>-2007 (-----) [006] ...1 14594.221989: tracing_mark_write: B|2007|topResumedActivityChangeItem - <...>-303 (-----) [007] d..2 14594.222016: sched_switch: prev_comm=crtc_event:97 prev_pid=303 prev_prio=83 prev_state=S ==> next_comm=rcu_preempt next_pid=7 next_prio=120 - rcu_preempt-7 ( 7) [007] d..2 14594.222035: sched_switch: prev_comm=rcu_preempt prev_pid=7 prev_prio=120 prev_state=S ==> next_comm=RenderThread next_pid=2738 next_prio=110 - migration/4-46 ( 46) [004] d..2 14594.222037: sched_switch: prev_comm=migration/4 prev_pid=46 prev_prio=0 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=110 - <...>-2738 (-----) [007] d..2 14594.222039: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=S ==> next_comm=kworker/u16:18 next_pid=18150 next_prio=120 - <...>-656 (-----) [004] ...1 14594.222100: tracing_mark_write: B|625|dequeueBuffer - <...>-656 (-----) [004] ...1 14594.222114: tracing_mark_write: B|625|com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#1: 2 - <...>-2007 (-----) [006] ...2 14594.222131: binder_set_priority: proc=1368 thread=1903 old=120 => new=110 desired=110 - <...>-2007 (-----) [006] d..2 14594.222143: sched_switch: prev_comm=s.nexuslauncher prev_pid=2007 prev_prio=110 prev_state=S ==> next_comm=UiThreadHelper next_pid=2045 next_prio=118 - <...>-2613 (-----) [001] d..2 14594.222158: sched_switch: prev_comm=ogle.android.as prev_pid=2613 prev_prio=120 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-18150 (-----) [007] d..2 14594.222193: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=S ==> next_comm=swapper/7 next_pid=0 next_prio=120 - <...>-656 (-----) [004] .... 14594.222220: binder_set_priority: proc=625 thread=656 old=110 => new=120 desired=120 - <...>-2738 (-----) [007] ...1 14594.222267: tracing_mark_write: B|2007|HWC release fence 36027 has signaled - <...>-656 (-----) [007] ...1 14594.223842: tracing_mark_write: B|625|queueBuffer - <...>-656 (-----) [007] ...1 14594.223845: tracing_mark_write: B|625|com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#1: 2 - <...>-656 (-----) [007] ...1 14594.223871: tracing_mark_write: B|625|requestNextVsync - <...>-656 (-----) [007] ...1 14594.223873: tracing_mark_write: B|625|resetIdleTimer - <...>-656 (-----) [007] ...1 14594.223881: tracing_mark_write: B|625|addAndGetFrameTimestamps - <...>-1395 (-----) [001] d..2 14594.223909: sched_switch: prev_comm=android.ui prev_pid=1395 prev_prio=118 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-2738 (-----) [007] ...1 14594.223959: tracing_mark_write: B|2007|Trace GPU completion fence 36027 - <...>-11799 (-----) [006] ...1 14594.224006: tracing_mark_write: B|2007|waiting for GPU completion 36027 - <...>-11799 (-----) [006] ...1 14594.224009: tracing_mark_write: B|2007|waitForever - <...>-2613 (-----) [004] d..2 14594.224014: sched_switch: prev_comm=ogle.android.as prev_pid=2613 prev_prio=120 prev_state=S ==> next_comm=Binder:1803_6 next_pid=2173 next_prio=120 - <...>-11799 (-----) [006] d..1 14594.224014: fence_enable_signal: driver=kgsl-timeline timeline=kgsl-3d0_13-s.nexuslauncher(200 context=27 seqno=78002 - <...>-11799 (-----) [006] d..2 14594.224021: sched_switch: prev_comm=GPU completion prev_pid=11799 prev_prio=110 prev_state=S ==> next_comm=rcuop/6 next_pid=68 next_prio=120 - rcuop/6-68 ( 68) [006] d..2 14594.224044: sched_switch: prev_comm=rcuop/6 prev_pid=68 prev_prio=120 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120 - <...>-259 (-----) [006] d..2 14594.224132: sched_switch: prev_comm=kgsl_worker_thr prev_pid=259 prev_prio=97 prev_state=S ==> next_comm=Binder:2007_A next_pid=4180 next_prio=120 - <...>-3206 (-----) [001] d..2 14594.224167: sched_switch: prev_comm=aiai-vc-0 prev_pid=3206 prev_prio=139 prev_state=R ==> next_comm=ndroid.systemui next_pid=1803 next_prio=120 - lowpool[847]-14589 ( 2446) [005] d..1 14594.224300: mm_filemap_delete_from_page_cache: dev 0:1 ino 3d0034 page=000000008247d586 pfn=676904 ofs=0 - <...>-1803 (-----) [001] d..2 14594.224302: sched_switch: prev_comm=ndroid.systemui prev_pid=1803 prev_prio=120 prev_state=S ==> next_comm=aiai-vc-0 next_pid=3206 next_prio=139 - <...>-3206 (-----) [001] d..2 14594.224433: sched_switch: prev_comm=aiai-vc-0 prev_pid=3206 prev_prio=139 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-1903 (-----) [003] ...1 14594.224490: tracing_mark_write: B|1368|dispatchingStartProcess:com.google.android.dialer - <...>-1903 (-----) [003] ...1 14594.224659: tracing_mark_write: B|1368|wmLayout - <...>-1903 (-----) [003] ...1 14594.224666: tracing_mark_write: B|1368|performSurfacePlacement - <...>-1903 (-----) [003] ...1 14594.224683: tracing_mark_write: B|1368|applySurfaceChanges - <...>-1903 (-----) [003] ...1 14594.224688: tracing_mark_write: B|1368|openSurfaceTransaction - <...>-2738 (-----) [007] ...1 14594.224711: tracing_mark_write: B|2007|query - <...>-1903 (-----) [003] ...1 14594.224714: tracing_mark_write: B|1368|performLayout - <...>-2738 (-----) [007] ...1 14594.224714: tracing_mark_write: B|2007|query - <...>-1903 (-----) [003] ...1 14594.224723: tracing_mark_write: B|1368|applyPostLayoutPolicy - <...>-2738 (-----) [007] d..2 14594.224752: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=110 - <...>-656 (-----) [007] .... 14594.224766: binder_set_priority: proc=625 thread=656 old=110 => new=120 desired=120 - <...>-1398 (-----) [002] ...1 14594.224801: tracing_mark_write: B|1368|Theme::ApplyStyle - <...>-1398 (-----) [002] ...1 14594.224805: tracing_mark_write: B|1368|AssetManager::GetBag - <...>-1398 (-----) [002] ...1 14594.224820: tracing_mark_write: B|1368|AssetManager::GetBag - <...>-1398 (-----) [002] ...1 14594.224826: tracing_mark_write: B|1368|AssetManager::GetBag - <...>-1398 (-----) [002] ...1 14594.224833: tracing_mark_write: B|1368|AssetManager::GetBag - <...>-1398 (-----) [002] ...1 14594.224838: tracing_mark_write: B|1368|AssetManager::GetBag - <...>-1398 (-----) [002] ...1 14594.224846: tracing_mark_write: B|1368|AssetManager::GetBag - <...>-1398 (-----) [002] ...1 14594.224853: tracing_mark_write: B|1368|AssetManager::GetBag - <...>-1398 (-----) [002] ...1 14594.224859: tracing_mark_write: B|1368|AssetManager::GetBag - <...>-1398 (-----) [002] ...1 14594.224864: tracing_mark_write: B|1368|AssetManager::GetBag - <...>-18150 (-----) [006] d..2 14594.228407: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=R+ ==> next_comm=mmc-cmdqd/0 next_pid=440 next_prio=98 - <...>-2738 (-----) [007] d..2 14594.228411: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=R+ ==> next_comm=kworker/7:0H next_pid=76 next_prio=100 - <...>-1409 (-----) [004] ...1 14594.228417: tracing_mark_write: B|1368|Start proc: com.google.android.dialer - <...>-440 (-----) [006] d..2 14594.228418: sched_switch: prev_comm=mmc-cmdqd/0 prev_pid=440 prev_prio=98 prev_state=D ==> next_comm=kworker/u16:18 next_pid=18150 next_prio=120 - <...>-76 (-----) [007] d..2 14594.228430: sched_switch: prev_comm=kworker/7:0H prev_pid=76 prev_prio=100 prev_state=R+ ==> next_comm=mmc-cmdqd/0 next_pid=440 next_prio=98 - <...>-440 (-----) [007] d..2 14594.228434: sched_switch: prev_comm=mmc-cmdqd/0 prev_pid=440 prev_prio=98 prev_state=D ==> next_comm=kworker/7:0H next_pid=76 next_prio=100 - <...>-18150 (-----) [006] d..3 14594.228442: sched_blocked_reason: pid=1398 iowait=1 caller=wait_on_page_bit_common+0x2a8/0x5f8 - <...>-76 (-----) [007] d..2 14594.228442: sched_switch: prev_comm=kworker/7:0H prev_pid=76 prev_prio=100 prev_state=S ==> next_comm=RenderThread next_pid=2738 next_prio=110 - <...>-2738 (-----) [007] ...2 14594.228446: binder_set_priority: proc=625 thread=656 old=120 => new=110 desired=110 - <...>-18150 (-----) [006] d..2 14594.228447: sched_switch: prev_comm=kworker/u16:18 prev_pid=18150 prev_prio=120 prev_state=R+ ==> next_comm=android.anim next_pid=1398 next_prio=110 - <...>-2738 (-----) [007] d..2 14594.228479: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=110 - <...>-1409 (-----) [004] d..2 14594.228499: sched_switch: prev_comm=ActivityManager prev_pid=1409 prev_prio=118 prev_state=D ==> next_comm=Binder:965_2 next_pid=1041 next_prio=120 - <...>-625 (-----) [003] ...1 14594.229271: tracing_mark_write: B|625|handleTransaction - <...>-1773 (-----) [004] .... 14594.229285: binder_set_priority: proc=625 thread=1773 old=110 => new=120 desired=120 - <...>-440 (-----) [007] d..2 14594.229301: sched_switch: prev_comm=mmc-cmdqd/0 prev_pid=440 prev_prio=98 prev_state=D ==> next_comm=RenderThread next_pid=2738 next_prio=110 - <...>-2738 (-----) [007] ...1 14594.229318: tracing_mark_write: B|2007|HWC release fence 36028 has signaled - <...>-2738 (-----) [007] ...1 14594.229331: tracing_mark_write: B|2007|query - <...>-2738 (-----) [007] ...1 14594.229337: tracing_mark_write: B|2007|eglBeginFrame - <...>-2738 (-----) [007] ...1 14594.229352: tracing_mark_write: B|2007|query - <...>-2738 (-----) [007] ...1 14594.229354: tracing_mark_write: B|2007|query - <...>-791 (-----) [000] d..2 14594.229357: sched_switch: prev_comm=main prev_pid=791 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120 - <...>-625 (-----) [003] ...1 14594.229440: tracing_mark_write: B|625|doTransaction - <...>-13916 (-----) [002] d..2 14594.229482: sched_switch: prev_comm=HeapTaskDaemon prev_pid=13916 prev_prio=124 prev_state=D|K ==> next_comm=swapper/2 next_pid=0 next_prio=120 - <...>-13917 (-----) [001] d..2 14594.229492: sched_blocked_reason: pid=13916 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-625 (-----) [003] ...1 14594.229492: tracing_mark_write: B|625|doTransaction - <...>-625 (-----) [003] ...1 14594.229507: tracing_mark_write: B|625|doTransaction - <...>-13917 (-----) [001] d..2 14594.229523: sched_switch: prev_comm=ReferenceQueueD prev_pid=13917 prev_prio=124 prev_state=D ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-13916 (-----) [002] d..2 14594.229535: sched_blocked_reason: pid=13917 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-625 (-----) [003] ...1 14594.229538: tracing_mark_write: B|625|doTransaction - <...>-2738 (-----) [007] ...1 14594.229543: tracing_mark_write: B|2007|flush commands - <...>-13916 (-----) [002] .... 14594.229562: sched_process_exit: comm=HeapTaskDaemon pid=13916 prio=124 - <...>-625 (-----) [003] ...1 14594.229567: tracing_mark_write: B|625|doTransaction - <...>-625 (-----) [003] ...1 14594.229588: tracing_mark_write: B|625|doTransaction - <...>-625 (-----) [003] ...1 14594.229628: tracing_mark_write: B|625|doTransaction - <...>-625 (-----) [003] ...1 14594.229652: tracing_mark_write: B|625|doTransaction - <...>-13916 (-----) [002] d..2 14594.229676: sched_switch: prev_comm=HeapTaskDaemon prev_pid=13916 prev_prio=124 prev_state=x ==> next_comm=swapper/2 next_pid=0 next_prio=120 - <...>-625 (-----) [003] ...1 14594.229676: tracing_mark_write: B|625|doTransaction - <...>-2007 (-----) [006] d..2 14594.229688: sched_switch: prev_comm=s.nexuslauncher prev_pid=2007 prev_prio=110 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120 - <...>-625 (-----) [003] ...1 14594.229703: tracing_mark_write: B|625|doTransaction - <...>-625 (-----) [003] ...1 14594.229725: tracing_mark_write: B|625|doTransaction - <...>-625 (-----) [003] ...1 14594.229750: tracing_mark_write: B|625|doTransaction - <...>-625 (-----) [003] ...1 14594.229772: tracing_mark_write: B|625|doTransaction - <...>-625 (-----) [003] ...1 14594.229792: tracing_mark_write: B|625|doTransaction - <...>-791 (-----) [000] d..2 14594.229811: sched_switch: prev_comm=main prev_pid=791 prev_prio=120 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120 - <...>-625 (-----) [003] ...1 14594.229824: tracing_mark_write: B|625|doTransaction - <...>-2738 (-----) [007] ...1 14594.229827: tracing_mark_write: B|2007|eglSwapBuffersWithDamageKHR - <...>-13917 (-----) [001] d..2 14594.229836: sched_switch: prev_comm=ReferenceQueueD prev_pid=13917 prev_prio=124 prev_state=D ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-2738 (-----) [007] ...1 14594.229837: tracing_mark_write: B|2007|setSurfaceDamage - <...>-625 (-----) [003] ...1 14594.229850: tracing_mark_write: B|625|doTransaction - <...>-13918 (-----) [002] d..2 14594.229856: sched_blocked_reason: pid=13917 iowait=0 caller=SyS_madvise+0xd34/0xd3c - <...>-5281 (-----) [001] d..2 14594.229932: sched_switch: prev_comm=writer prev_pid=5281 prev_prio=96 prev_state=D ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-89 (-----) [006] d..2 14594.229951: sched_switch: prev_comm=lpass_smem_glin prev_pid=89 prev_prio=98 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120 - <...>-625 (-----) [003] ...1 14594.229982: tracing_mark_write: B|625|handleMessageInvalidate - <...>-625 (-----) [003] ...1 14594.229984: tracing_mark_write: B|625|handlePageFlip - <...>-625 (-----) [003] ...1 14594.230013: tracing_mark_write: B|625|latchBuffer - <...>-13917 (-----) [000] .... 14594.230015: sched_process_exit: comm=ReferenceQueueD pid=13917 prio=124 - <...>-625 (-----) [003] ...1 14594.230020: tracing_mark_write: B|625|query - <...>-625 (-----) [003] ...1 14594.230028: tracing_mark_write: B|625|updateTexImage - <...>-625 (-----) [003] ...1 14594.230035: tracing_mark_write: B|625|acquireBuffer - <...>-625 (-----) [003] ...1 14594.230044: tracing_mark_write: B|625|com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#1: 2 - <...>-2738 (-----) [007] d..2 14594.230057: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=D ==> next_comm=smem_native_lpa next_pid=88 next_prio=120 - <...>-14607 (-----) [000] d..2 14594.259609: sched_blocked_reason: pid=14624 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-2738 (-----) [005] d..2 14594.259620: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=120 prev_state=S ==> next_comm=Binder:625_4 next_pid=1773 next_prio=120 - <...>-1773 (-----) [005] ...1 14594.259649: tracing_mark_write: B|625|query - <...>-2738 (-----) [005] ...1 14594.259714: tracing_mark_write: B|2007|query - <...>-2738 (-----) [005] d..2 14594.259743: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=120 prev_state=S ==> next_comm=Binder:625_4 next_pid=1773 next_prio=120 - <...>-1773 (-----) [005] ...1 14594.259757: tracing_mark_write: B|625|query - <...>-2738 (-----) [005] ...1 14594.259810: tracing_mark_write: B|2007|syncFrameState - <...>-2738 (-----) [005] ...1 14594.259856: tracing_mark_write: B|2007|prepareTree - Binder:14607_1-14624 (14607) [002] ...1 14594.259863: tracing_mark_write: B|14607|AttachCurrentThread - Binder:14607_1-14624 (14607) [002] ...1 14594.259869: tracing_mark_write: B|14607|Thread::Attach - Binder:14607_1-14624 (14607) [002] ...1 14594.259873: tracing_mark_write: B|14607|Thread birth - Binder:14607_1-14624 (14607) [002] ...1 14594.259916: tracing_mark_write: B|14607|Thread::Init - Binder:14607_1-14624 (14607) [002] ...1 14594.259920: tracing_mark_write: B|14607|InitStackHwm - <...>-14607 (-----) [000] d..2 14594.259932: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_1-14624 (14607) [002] d..2 14594.259941: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-3198 (-----) [001] ...1 14594.259942: tracing_mark_write: B|2007|Update SurfaceView position - Binder:14607_1-14624 (14607) [002] ...1 14594.259963: tracing_mark_write: B|14607|InitTlsEntryPoints - Binder:14607_1-14624 (14607) [002] ...1 14594.259974: tracing_mark_write: B|14607|InitInterpreterTls - <...>-14607 (-----) [000] d..2 14594.260005: sched_blocked_reason: pid=14624 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-3198 (-----) [001] d..2 14594.260007: sched_switch: prev_comm=hwuiTask1 prev_pid=3198 prev_prio=118 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-14607 (-----) [000] d..2 14594.260024: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_1-14624 (14607) [002] d..2 14594.260038: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-14607 (-----) [000] d..2 14594.260064: sched_blocked_reason: pid=14624 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - Binder:14607_1-14624 (14607) [002] ...1 14594.260101: tracing_mark_write: B|14607|ThreadList::Register - <...>-2738 (-----) [005] ...1 14594.260128: tracing_mark_write: B|2007|query - <...>-2738 (-----) [005] ...1 14594.260140: tracing_mark_write: B|2007|query - <...>-2738 (-----) [005] ...1 14594.260148: tracing_mark_write: B|2007|query - <...>-2738 (-----) [005] ...1 14594.260155: tracing_mark_write: B|2007|query - <...>-2738 (-----) [005] ...1 14594.260161: tracing_mark_write: B|2007|query - <...>-2738 (-----) [005] ...1 14594.260167: tracing_mark_write: B|2007|query - <...>-2738 (-----) [005] ...1 14594.260173: tracing_mark_write: B|2007|dequeueBuffer - <...>-2007 (-----) [001] d..2 14594.260201: sched_switch: prev_comm=s.nexuslauncher prev_pid=2007 prev_prio=120 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-2738 (-----) [005] d..2 14594.260214: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=120 prev_state=S ==> next_comm=Binder:625_4 next_pid=1773 next_prio=120 - <...>-1773 (-----) [005] ...1 14594.260236: tracing_mark_write: B|625|dequeueBuffer - <...>-1773 (-----) [005] ...1 14594.260249: tracing_mark_write: B|625|com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#1: 2 - <...>-14607 (-----) [000] d..2 14594.260334: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_1-14624 (14607) [002] d..2 14594.260343: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-14607 (-----) [000] d..2 14594.260376: sched_blocked_reason: pid=14624 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-14607 (-----) [000] d..2 14594.260387: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - <...>-2738 (-----) [005] ...1 14594.260401: tracing_mark_write: B|2007|HWC release fence 36030 has signaled - Binder:14607_1-14624 (14607) [002] d..2 14594.260407: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-2738 (-----) [005] ...1 14594.260419: tracing_mark_write: B|2007|query - <...>-2738 (-----) [005] ...1 14594.260427: tracing_mark_write: B|2007|eglBeginFrame - <...>-2738 (-----) [005] ...1 14594.260445: tracing_mark_write: B|2007|query - <...>-2738 (-----) [005] ...1 14594.260450: tracing_mark_write: B|2007|query - Binder:14607_1-14624 (14607) [002] .... 14594.260472: task_newtask: pid=14625 comm=Binder:14607_1 clone_flags=3d0f00 oom_score_adj=-1000 - <...>-14607 (-----) [000] d..2 14594.260517: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_2-14625 (14607) [001] d..2 14594.260525: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-14607 (-----) [000] d..2 14594.260555: sched_blocked_reason: pid=14625 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-14607 (-----) [000] ...1 14594.260569: tracing_mark_write: B|14607|ActivityThreadMain - <...>-14607 (-----) [000] d..2 14594.260581: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_2-14625 (14607) [001] d..2 14594.260588: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-14607 (-----) [000] d..2 14594.260611: sched_blocked_reason: pid=14625 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-14607 (-----) [000] d..2 14594.260623: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_2-14625 (14607) [001] d..2 14594.260636: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-14607 (-----) [000] d..2 14594.260663: sched_blocked_reason: pid=14625 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-14607 (-----) [000] d..2 14594.260674: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_2-14625 (14607) [001] d..2 14594.260694: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-14607 (-----) [000] d..2 14594.260724: sched_blocked_reason: pid=14625 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-2738 (-----) [005] ...1 14594.260734: tracing_mark_write: B|2007|flush commands - <...>-14607 (-----) [000] d..2 14594.260735: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_2-14625 (14607) [001] d..2 14594.260753: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - Binder:14607_2-14625 (14607) [001] ...1 14594.260925: tracing_mark_write: B|14607|AttachCurrentThread - Binder:14607_2-14625 (14607) [001] ...1 14594.260930: tracing_mark_write: B|14607|Thread::Attach - Binder:14607_2-14625 (14607) [001] ...1 14594.260933: tracing_mark_write: B|14607|Thread birth - Binder:14607_2-14625 (14607) [001] ...1 14594.260973: tracing_mark_write: B|14607|Thread::Init - Binder:14607_2-14625 (14607) [001] ...1 14594.260977: tracing_mark_write: B|14607|InitStackHwm - <...>-14607 (-----) [000] d..2 14594.260990: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_2-14625 (14607) [001] d..2 14594.260998: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - Binder:14607_2-14625 (14607) [001] ...1 14594.261023: tracing_mark_write: B|14607|InitTlsEntryPoints - Binder:14607_2-14625 (14607) [001] ...1 14594.261034: tracing_mark_write: B|14607|InitInterpreterTls - <...>-14607 (-----) [000] d..2 14594.261064: sched_blocked_reason: pid=14625 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-14607 (-----) [000] d..2 14594.261075: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_2-14625 (14607) [001] d..2 14594.261094: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-14607 (-----) [000] d..2 14594.261120: sched_blocked_reason: pid=14625 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-14607 (-----) [000] d..2 14594.261132: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - Binder:14607_2-14625 (14607) [001] d..2 14594.261146: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - Binder:14607_2-14625 (14607) [001] ...1 14594.261167: tracing_mark_write: B|14607|ThreadList::Register - <...>-14607 (-----) [000] d..2 14594.261209: sched_blocked_reason: pid=14625 iowait=0 caller=__rwsem_down_write_failed_common+0x3e8/0x754 - <...>-2738 (-----) [005] ...1 14594.261212: tracing_mark_write: B|2007|waitOnFences - <...>-14607 (-----) [000] d..2 14594.261220: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=120 prev_state=D ==> next_comm=swapper/0 next_pid=0 next_prio=120 - <...>-2738 (-----) [005] ...1 14594.261232: tracing_mark_write: B|2007|eglSwapBuffersWithDamageKHR - <...>-2738 (-----) [005] ...1 14594.261244: tracing_mark_write: B|2007|setSurfaceDamage - Binder:14607_2-14625 (14607) [001] d..2 14594.261246: sched_blocked_reason: pid=14607 iowait=0 caller=do_page_fault+0x550/0x5fc - <...>-14607 (-----) [000] ...1 14594.261326: tracing_mark_write: B|14607|VerifyClass com.android.org.conscrypt.TrustedCertificateStore$PreloadHolder - <...>-2738 (-----) [005] .... 14594.261621: fence_init: driver=kgsl-timeline timeline=kgsl-3d0_13-s.nexuslauncher(200 context=27 seqno=78005 - <...>-625 (-----) [003] ...1 14594.263903: tracing_mark_write: B|625|resetIdleTimer - <...>-625 (-----) [003] ...1 14594.263912: tracing_mark_write: B|625|rebuildLayerStacks - <...>-625 (-----) [003] ...1 14594.263915: tracing_mark_write: B|625|rebuildLayerStacks VR Dirty - <...>-625 (-----) [003] ...1 14594.263919: tracing_mark_write: B|625|computeVisibleRegions - <...>-1398 (-----) [006] d..2 14594.263966: sched_switch: prev_comm=android.anim prev_pid=1398 prev_prio=110 prev_state=S ==> next_comm=Binder:625_4 next_pid=1773 next_prio=120 - <...>-1695 (-----) [001] d..2 14594.264086: sched_switch: prev_comm=InputDispatcher prev_pid=1695 prev_prio=112 prev_state=S ==> next_comm=Binder:1368_14 next_pid=3253 next_prio=120 - <...>-625 (-----) [003] ...1 14594.264293: tracing_mark_write: B|625|calculateWorkingSet - <...>-625 (-----) [003] ...1 14594.264500: tracing_mark_write: B|625|prepare - <...>-625 (-----) [003] ...1 14594.264513: tracing_mark_write: B|625|HIDL::IComposerClient::executeCommands_2_2::client - <...>-625 (-----) [003] ...2 14594.264584: binder_set_priority: proc=627 thread=627 old=97 => new=98 desired=98 - <...>-625 (-----) [003] d..2 14594.264617: sched_switch: prev_comm=surfaceflinger prev_pid=625 prev_prio=98 prev_state=S ==> next_comm=logd.writer next_pid=588 next_prio=130 - <...>-588 (-----) [003] d..2 14594.264851: sched_switch: prev_comm=logd.writer prev_pid=588 prev_prio=130 prev_state=S ==> next_comm=swapper/3 next_pid=0 next_prio=120 - rcu_preempt-7 ( 7) [007] d..2 14594.265273: sched_switch: prev_comm=rcu_preempt prev_pid=7 prev_prio=120 prev_state=S ==> next_comm=kworker/u16:3 next_pid=18008 next_prio=120 - <...>-18008 (-----) [007] d..2 14594.265404: sched_switch: prev_comm=kworker/u16:3 prev_pid=18008 prev_prio=120 prev_state=D ==> next_comm=swapper/7 next_pid=0 next_prio=120 - <...>-18008 (-----) [007] d..2 14594.265471: sched_switch: prev_comm=kworker/u16:3 prev_pid=18008 prev_prio=120 prev_state=S ==> next_comm=swapper/7 next_pid=0 next_prio=120 - <...>-625 (-----) [003] ...1 14594.265496: tracing_mark_write: B|625|doComposition - <...>-625 (-----) [003] ...1 14594.265507: tracing_mark_write: B|625|doComposeSurfaces - <...>-625 (-----) [003] ...1 14594.265552: tracing_mark_write: B|625|acquireBuffer - <...>-625 (-----) [003] ...1 14594.265563: tracing_mark_write: B|625|postFramebuffer - <...>-625 (-----) [003] ...1 14594.265567: tracing_mark_write: B|625|presentAndGetReleaseFences - <...>-625 (-----) [003] d..1 14594.265601: fence_enable_signal: driver=sde_fence:crtc97:91650 timeline=crtc97 context=3 seqno=91650 - <...>-625 (-----) [003] ...1 14594.265735: tracing_mark_write: B|625|logLayerStats - <...>-625 (-----) [003] ...1 14594.265744: tracing_mark_write: B|625|postComposition - <...>-625 (-----) [003] ...1 14594.265749: tracing_mark_write: B|625|releaseBuffer - <...>-625 (-----) [003] ...1 14594.265753: tracing_mark_write: B|625|com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#1: 1 - <...>-625 (-----) [003] ...1 14594.265791: tracing_mark_write: B|625|releaseBuffer - <...>-440 (-----) [007] d..2 14594.342366: sched_switch: prev_comm=mmc-cmdqd/0 prev_pid=440 prev_prio=98 prev_state=D ==> next_comm=kworker/u17:2 next_pid=1778 next_prio=100 - <...>-2007 (-----) [006] ...1 14594.342375: tracing_mark_write: B|2007|input - <...>-2007 (-----) [006] ...1 14594.342399: tracing_mark_write: B|2007|animation - <...>-625 (-----) [003] ...1 14594.342447: tracing_mark_write: B|625|doTransaction - <...>-625 (-----) [003] ...1 14594.342489: tracing_mark_write: B|625|doTransaction - kworker/u17:2-1778 ( 1778) [007] d..3 14594.342532: sched_blocked_reason: pid=14607 iowait=1 caller=wait_on_page_bit_common+0x2a8/0x5f8 - kworker/u17:2-1778 ( 1778) [007] d..2 14594.342544: sched_switch: prev_comm=kworker/u17:2 prev_pid=1778 prev_prio=100 prev_state=S ==> next_comm=kworker/u16:2 next_pid=27544 next_prio=120 - <...>-1773 (-----) [000] ...1 14594.342575: tracing_mark_write: B|625|requestNextVsync - <...>-1773 (-----) [000] ...1 14594.342579: tracing_mark_write: B|625|resetIdleTimer - <...>-27544 (-----) [007] d..2 14594.342589: sched_switch: prev_comm=kworker/u16:2 prev_pid=27544 prev_prio=120 prev_state=S ==> next_comm=swapper/7 next_pid=0 next_prio=120 - <...>-656 (-----) [002] d.h3 14594.342604: sched_blocked_reason: pid=1233 iowait=0 caller=geni_i2c_xfer+0x4d8/0x1398 - <...>-1803 (-----) [001] d..2 14594.342605: sched_switch: prev_comm=ndroid.systemui prev_pid=1803 prev_prio=120 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-625 (-----) [003] ...1 14594.342632: tracing_mark_write: B|625|handleMessageInvalidate - <...>-625 (-----) [003] ...1 14594.342634: tracing_mark_write: B|625|handlePageFlip - <...>-2738 (-----) [007] ...1 14594.342641: tracing_mark_write: B|2007|notifyFramePending - <...>-658 (-----) [002] d..2 14594.342653: sched_switch: prev_comm=DispSync prev_pid=658 prev_prio=97 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=120 - <...>-656 (-----) [002] ...1 14594.342656: tracing_mark_write: B|625|requestNextVsync - <...>-2738 (-----) [007] d..2 14594.342658: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=S ==> next_comm=swapper/7 next_pid=0 next_prio=120 - <...>-656 (-----) [002] ...1 14594.342660: tracing_mark_write: B|625|resetIdleTimer - <...>-660 (-----) [005] d..2 14594.342663: sched_switch: prev_comm=app prev_pid=660 prev_prio=97 prev_state=S ==> next_comm=swapper/5 next_pid=0 next_prio=120 - <...>-625 (-----) [003] ...1 14594.342665: tracing_mark_write: B|625|latchBuffer - <...>-625 (-----) [003] ...1 14594.342673: tracing_mark_write: B|625|query - <...>-625 (-----) [003] ...1 14594.342682: tracing_mark_write: B|625|updateTexImage - <...>-625 (-----) [003] ...1 14594.342693: tracing_mark_write: B|625|acquireBuffer - <...>-625 (-----) [003] ...1 14594.342703: tracing_mark_write: B|625|com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#1: 1 - <...>-660 (-----) [005] d..2 14594.342709: sched_switch: prev_comm=app prev_pid=660 prev_prio=97 prev_state=S ==> next_comm=swapper/5 next_pid=0 next_prio=120 - <...>-2007 (-----) [006] ...1 14594.342733: tracing_mark_write: B|2007|traversal - <...>-2007 (-----) [006] ...1 14594.342776: tracing_mark_write: B|2007|draw - <...>-2007 (-----) [006] ...1 14594.342791: tracing_mark_write: B|2007|Record View#draw() - <...>-625 (-----) [003] ...1 14594.342849: tracing_mark_write: B|625|updateInputFlinger - <...>-2007 (-----) [006] d..2 14594.342903: sched_switch: prev_comm=s.nexuslauncher prev_pid=2007 prev_prio=110 prev_state=S ==> next_comm=kworker/6:2H next_pid=24261 next_prio=100 - <...>-2738 (-----) [007] ...1 14594.342910: tracing_mark_write: B|2007|DrawFrame - <...>-2738 (-----) [007] d..2 14594.342917: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=R+ ==> next_comm=mmc-cmdqd/0 next_pid=440 next_prio=98 - <...>-24261 (-----) [006] d..2 14594.342918: sched_switch: prev_comm=kworker/6:2H prev_pid=24261 prev_prio=100 prev_state=S ==> next_comm=.android.dialer next_pid=14607 next_prio=110 - <...>-440 (-----) [007] d..2 14594.342926: sched_switch: prev_comm=mmc-cmdqd/0 prev_pid=440 prev_prio=98 prev_state=D ==> next_comm=RenderThread next_pid=2738 next_prio=110 - <...>-2738 (-----) [007] ...1 14594.342927: tracing_mark_write: B|2007|query - <...>-2738 (-----) [007] ...2 14594.342959: binder_set_priority: proc=625 thread=656 old=120 => new=110 desired=110 - <...>-2738 (-----) [007] d..2 14594.342975: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=R+ ==> next_comm=Binder:625_1 next_pid=656 next_prio=110 - <...>-656 (-----) [007] ...1 14594.343021: tracing_mark_write: B|625|query - <...>-656 (-----) [007] .... 14594.343033: binder_set_priority: proc=625 thread=656 old=110 => new=120 desired=120 - <...>-2738 (-----) [007] ...1 14594.343070: tracing_mark_write: B|2007|query - <...>-1233 (-----) [004] d..2 14594.343074: sched_switch: prev_comm=sound trigger c prev_pid=1233 prev_prio=120 prev_state=R+ ==> next_comm=irq/144-1436400 next_pid=2522 next_prio=49 - <...>-2738 (-----) [007] ...2 14594.343078: binder_set_priority: proc=625 thread=656 old=120 => new=110 desired=110 - <...>-625 (-----) [003] ...1 14594.343084: tracing_mark_write: B|625|onMessageReceived - <...>-625 (-----) [003] ...1 14594.343087: tracing_mark_write: B|625|handleMessageRefresh - <...>-625 (-----) [003] ...1 14594.343090: tracing_mark_write: B|625|preComposition - <...>-2738 (-----) [007] d..2 14594.343090: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=R+ ==> next_comm=Binder:625_1 next_pid=656 next_prio=110 - <...>-625 (-----) [003] ...1 14594.343122: tracing_mark_write: B|625|rebuildLayerStacks - <...>-625 (-----) [003] ...1 14594.343124: tracing_mark_write: B|625|rebuildLayerStacks VR Dirty - <...>-89 (-----) [007] d..2 14594.343126: sched_switch: prev_comm=lpass_smem_glin prev_pid=89 prev_prio=98 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=110 - <...>-625 (-----) [003] ...1 14594.343129: tracing_mark_write: B|625|computeVisibleRegions - <...>-656 (-----) [007] ...1 14594.343136: tracing_mark_write: B|625|query - <...>-14607 (-----) [006] ...2 14594.343141: binder_set_priority: proc=1368 thread=3253 old=120 => new=110 desired=110 - <...>-2965 (-----) [001] .... 14596.746610: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=000000002ae8fcff pfn=1522884 ofs=188416 - <idle>-0 (-----) [002] d..2 14596.746619: sched_switch: prev_comm=swapper/2 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=mmc-cmdqd/0 next_pid=440 next_prio=98 - <...>-2965 (-----) [001] .... 14596.746629: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=00000000679ee1ec pfn=1299913 ofs=192512 - <...>-2965 (-----) [001] .... 14596.746664: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=0000000006cd2fb7 pfn=1296251 ofs=196608 - <...>-2965 (-----) [001] .... 14596.746677: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=00000000af82f3d6 pfn=1419330 ofs=200704 - <...>-2965 (-----) [001] .... 14596.746693: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=000000002840f054 pfn=1304928 ofs=204800 - <...>-2965 (-----) [001] .... 14596.746706: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=000000004a59da17 pfn=1288069 ofs=208896 - <...>-2965 (-----) [001] .... 14596.746717: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=0000000023a80dca pfn=1419686 ofs=212992 - <...>-2965 (-----) [001] .... 14596.746730: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=000000001cf89eab pfn=1315372 ofs=217088 - <...>-2965 (-----) [001] .... 14596.746743: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=000000005b4c6cb6 pfn=1380698 ofs=221184 - <...>-2965 (-----) [001] .... 14596.746760: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=00000000f8304ae7 pfn=1206753 ofs=225280 - <...>-2965 (-----) [001] .... 14596.746773: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=00000000cb912305 pfn=1325465 ofs=229376 - <...>-2965 (-----) [001] .... 14596.746785: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=00000000f16f3774 pfn=1408056 ofs=233472 - <...>-2965 (-----) [001] .... 14596.746801: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=0000000056d4c926 pfn=1418352 ofs=237568 - <...>-2965 (-----) [001] .... 14596.746815: mm_filemap_add_to_page_cache: dev 253:6 ino a359 page=00000000f3eeb42c pfn=1320957 ofs=241664 - <...>-440 (-----) [002] d..2 14596.746916: sched_switch: prev_comm=mmc-cmdqd/0 prev_pid=440 prev_prio=98 prev_state=D ==> next_comm=swapper/2 next_pid=0 next_prio=120 - - <...>-656 (-----) [007] .... 14594.343145: binder_set_priority: proc=625 thread=656 old=110 => new=120 desired=120 - <...>-14607 (-----) [006] d..2 14594.343164: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=110 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120 - <...>-5281 (-----) [002] d..2 14594.343177: sched_switch: prev_comm=writer prev_pid=5281 prev_prio=96 prev_state=S ==> next_comm=RenderThread next_pid=2738 next_prio=110 - irq/144-1436400-2522 ( 2522) [004] d..2 14594.343223: sched_switch: prev_comm=irq/144-1436400 prev_pid=2522 prev_prio=49 prev_state=D ==> next_comm=sound trigger c next_pid=1233 next_prio=120 - <...>-88 (-----) [006] d..2 14594.343240: sched_switch: prev_comm=smem_native_lpa prev_pid=88 prev_prio=98 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120 - <...>-1238 (-----) [001] d..2 14594.343243: sched_switch: prev_comm=FastMixer prev_pid=1238 prev_prio=96 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-2738 (-----) [002] ...1 14594.343244: tracing_mark_write: B|2007|syncFrameState - <...>-2738 (-----) [002] ...1 14594.343293: tracing_mark_write: B|2007|prepareTree - <...>-1695 (-----) [001] d..2 14594.343318: sched_switch: prev_comm=InputDispatcher prev_pid=1695 prev_prio=112 prev_state=R+ ==> next_comm=FastMixer next_pid=1238 next_prio=96 - <...>-5281 (-----) [005] d..2 14594.343322: sched_switch: prev_comm=writer prev_pid=5281 prev_prio=96 prev_state=S ==> next_comm=Binder:1368_14 next_pid=3253 next_prio=110 - <...>-1238 (-----) [001] d..2 14594.343442: sched_switch: prev_comm=FastMixer prev_pid=1238 prev_prio=96 prev_state=S ==> next_comm=InputDispatcher next_pid=1695 next_prio=112 - <...>-1695 (-----) [001] d..2 14594.343467: sched_switch: prev_comm=InputDispatcher prev_pid=1695 prev_prio=112 prev_state=S ==> next_comm=swapper/1 next_pid=0 next_prio=120 - <...>-5281 (-----) [000] d..2 14594.343484: sched_switch: prev_comm=writer prev_pid=5281 prev_prio=96 prev_state=S ==> next_comm=swapper/0 next_pid=0 next_prio=120 - <...>-625 (-----) [003] ...1 14594.343519: tracing_mark_write: B|625|calculateWorkingSet - <...>-2738 (-----) [002] ...1 14594.343568: tracing_mark_write: B|2007|query - <...>-2738 (-----) [002] ...1 14594.343577: tracing_mark_write: B|2007|query - <...>-2738 (-----) [002] ...1 14594.343586: tracing_mark_write: B|2007|query - <...>-2738 (-----) [002] ...1 14594.343591: tracing_mark_write: B|2007|query - <...>-2738 (-----) [002] ...1 14594.343597: tracing_mark_write: B|2007|query - <...>-2738 (-----) [002] ...1 14594.343602: tracing_mark_write: B|2007|query - <...>-2738 (-----) [002] ...1 14594.343609: tracing_mark_write: B|2007|dequeueBuffer - <...>-2007 (-----) [006] d..2 14594.343612: sched_switch: prev_comm=s.nexuslauncher prev_pid=2007 prev_prio=110 prev_state=S ==> next_comm=swapper/6 next_pid=0 next_prio=120 - <...>-2738 (-----) [002] ...2 14594.343633: binder_set_priority: proc=625 thread=656 old=120 => new=110 desired=110 - <...>-2738 (-----) [002] d..2 14594.343683: sched_switch: prev_comm=RenderThread prev_pid=2738 prev_prio=110 prev_state=R+ ==> next_comm=Binder:625_1 next_pid=656 next_prio=110 - <...>-625 (-----) [003] ...1 14594.343704: tracing_mark_write: B|625|prepare - <...>-656 (-----) [002] ...1 14594.343707: tracing_mark_write: B|625|dequeueBuffer - <...>-625 (-----) [004] ...1 14594.812869: tracing_mark_write: B|625|com.google.android.dialer/com.google.android.dialer.extensions.GoogleDialtactsActivity#0: 2 - <...>-2048 (-----) [000] d..2 14594.812895: sched_switch: prev_comm=RenderThread prev_pid=2048 prev_prio=120 prev_state=R+ ==> next_comm=Binder:625_3 next_pid=1431 next_prio=120 - <...>-1431 (-----) [000] ...1 14594.812911: tracing_mark_write: B|625|query - <...>-625 (-----) [004] ...1 14594.812914: tracing_mark_write: B|625|latchBuffer - <...>-625 (-----) [004] ...1 14594.812919: tracing_mark_write: B|625|query - <...>-625 (-----) [004] ...1 14594.812925: tracing_mark_write: B|625|updateTexImage - <...>-625 (-----) [004] ...1 14594.812928: tracing_mark_write: B|625|acquireBuffer - <...>-625 (-----) [004] ...1 14594.812934: tracing_mark_write: B|625|StatusBar#0: 1 - <...>-2048 (-----) [000] ...1 14594.812962: tracing_mark_write: B|1803|syncFrameState - <...>-656 (-----) [002] ...1 14594.813044: tracing_mark_write: B|625|setTransactionState - <...>-14607 (-----) [007] ...2 14594.813083: binder_set_priority: proc=10691 thread=18733 old=120 => new=110 desired=110 - <...>-14607 (-----) [007] d..2 14594.813114: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=110 prev_state=S ==> next_comm=kworker/7:1 next_pid=7092 next_prio=120 - <...>-14655 (-----) [006] d..2 14594.813128: sched_switch: prev_comm=DialerExecutors prev_pid=14655 prev_prio=130 prev_state=R ==> next_comm=lpass_smem_glin next_pid=89 next_prio=98 - <...>-89 (-----) [006] d..2 14594.813163: sched_switch: prev_comm=lpass_smem_glin prev_pid=89 prev_prio=98 prev_state=S ==> next_comm=DialerExecutors next_pid=14655 next_prio=130 - <...>-656 (-----) [002] ...1 14594.813218: tracing_mark_write: B|625|requestNextVsync - <...>-656 (-----) [002] ...1 14594.813222: tracing_mark_write: B|625|resetIdleTimer - kworker/7:1-7092 ( 7092) [007] d..2 14594.813239: sched_switch: prev_comm=kworker/7:1 prev_pid=7092 prev_prio=120 prev_state=R+ ==> next_comm=smem_native_lpa next_pid=88 next_prio=98 - <...>-5281 (-----) [001] d..2 14594.813245: sched_switch: prev_comm=writer prev_pid=5281 prev_prio=96 prev_state=S ==> next_comm=Binder:10691_B next_pid=18733 next_prio=110 - <...>-88 (-----) [007] d..2 14594.813248: sched_switch: prev_comm=smem_native_lpa prev_pid=88 prev_prio=98 prev_state=R ==> next_comm=kgsl_worker_thr next_pid=259 next_prio=97 - <...>-2048 (-----) [000] d..2 14594.813249: sched_switch: prev_comm=RenderThread prev_pid=2048 prev_prio=120 prev_state=R+ ==> next_comm=FastMixer next_pid=1238 next_prio=96 - <...>-14655 (-----) [006] d..2 14594.813263: sched_switch: prev_comm=DialerExecutors prev_pid=14655 prev_prio=130 prev_state=R+ ==> next_comm=smem_native_lpa next_pid=88 next_prio=98 - <...>-661 (-----) [002] d..2 14594.813265: sched_switch: prev_comm=sf prev_pid=661 prev_prio=97 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=116 - <...>-259 (-----) [007] d..2 14594.813265: sched_switch: prev_comm=kgsl_worker_thr prev_pid=259 prev_prio=97 prev_state=S ==> next_comm=kworker/7:1 next_pid=7092 next_prio=120 - kworker/7:1-7092 ( 7092) [007] d..2 14594.813271: sched_switch: prev_comm=kworker/7:1 prev_pid=7092 prev_prio=120 prev_state=S ==> next_comm=system next_pid=108 next_prio=120 - <...>-108 (-----) [007] .... 14594.813275: ion_heap_shrink: heap_name=system, len=9469952, total_allocated=189620224 - <...>-88 (-----) [006] d..2 14594.813294: sched_switch: prev_comm=smem_native_lpa prev_pid=88 prev_prio=98 prev_state=S ==> next_comm=DialerExecutors next_pid=14655 next_prio=130 - <...>-625 (-----) [004] ...1 14594.813310: tracing_mark_write: B|625|updateInputFlinger - <...>-1238 (-----) [000] d..2 14594.813312: sched_switch: prev_comm=FastMixer prev_pid=1238 prev_prio=96 prev_state=S ==> next_comm=RenderThread next_pid=2048 next_prio=120 - <...>-661 (-----) [002] d..2 14594.813317: sched_switch: prev_comm=sf prev_pid=661 prev_prio=97 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=116 - <...>-14640 (-----) [005] d..2 14594.813319: sched_switch: prev_comm=DialerExecutors prev_pid=14640 prev_prio=130 prev_state=R ==> next_comm=DispSync next_pid=658 next_prio=97 - <...>-656 (-----) [002] ...1 14594.813336: tracing_mark_write: B|625|~GraphicBuffer - <...>-658 (-----) [005] d..2 14594.813345: sched_switch: prev_comm=DispSync prev_pid=658 prev_prio=97 prev_state=S ==> next_comm=DialerExecutors next_pid=14640 next_prio=130 - <...>-656 (-----) [002] ...1 14594.813345: tracing_mark_write: B|625|~GraphicBuffer - <...>-656 (-----) [002] ...1 14594.813353: tracing_mark_write: B|625|~GraphicBuffer - <...>-2048 (-----) [000] d..2 14594.813358: sched_switch: prev_comm=RenderThread prev_pid=2048 prev_prio=120 prev_state=R+ ==> next_comm=FastMixer next_pid=1238 next_prio=96 - <...>-656 (-----) [002] ...1 14594.813364: tracing_mark_write: B|625|~GraphicBuffer - <...>-5281 (-----) [001] d..2 14594.813369: sched_switch: prev_comm=writer prev_pid=5281 prev_prio=96 prev_state=S ==> next_comm=Binder:10691_B next_pid=18733 next_prio=110 - <...>-656 (-----) [002] ...1 14594.813372: tracing_mark_write: B|625|~GraphicBuffer - <...>-656 (-----) [002] ...1 14594.813380: tracing_mark_write: B|625|~GraphicBuffer - <...>-656 (-----) [002] ...1 14594.813391: tracing_mark_write: B|625|~GraphicBuffer - <...>-656 (-----) [002] ...1 14594.813398: tracing_mark_write: B|625|~GraphicBuffer - <...>-656 (-----) [002] ...1 14594.813408: tracing_mark_write: B|625|~GraphicBuffer - <...>-656 (-----) [002] ...1 14594.813416: tracing_mark_write: B|625|~GraphicBuffer - <...>-656 (-----) [002] ...1 14594.813424: tracing_mark_write: B|625|~GraphicBuffer - <...>-656 (-----) [002] ...1 14594.813432: tracing_mark_write: B|625|~GraphicBuffer - <...>-656 (-----) [002] .n.1 14594.813443: tracing_mark_write: B|625|~GraphicBuffer - <...>-1238 (-----) [000] d..2 14594.813464: sched_switch: prev_comm=FastMixer prev_pid=1238 prev_prio=96 prev_state=S ==> next_comm=RenderThread next_pid=2048 next_prio=120 - <...>-5281 (-----) [002] d..2 14594.813525: sched_switch: prev_comm=writer prev_pid=5281 prev_prio=96 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=116 - <...>-656 (-----) [002] ...1 14594.813544: tracing_mark_write: B|625|~GraphicBuffer - <...>-656 (-----) [002] ...1 14594.813557: tracing_mark_write: B|625|~GraphicBuffer - <...>-2048 (-----) [000] d..2 14594.813594: sched_switch: prev_comm=RenderThread prev_pid=2048 prev_prio=120 prev_state=R+ ==> next_comm=Binder:1368_15 next_pid=3359 next_prio=120 - <...>-18733 (-----) [001] ...2 14594.813635: binder_set_priority: proc=1368 thread=3514 old=120 => new=110 desired=110 - <...>-656 (-----) [002] .... 14594.813637: binder_set_priority: proc=625 thread=656 old=116 => new=120 desired=120 - <...>-108 (-----) [007] d..2 14594.813646: sched_switch: prev_comm=system prev_pid=108 prev_prio=120 prev_state=R+ ==> next_comm=android.anim next_pid=1398 next_prio=116 - <...>-625 (-----) [004] ...1 14594.813646: tracing_mark_write: B|625|onMessageReceived - <...>-625 (-----) [004] ...1 14594.813649: tracing_mark_write: B|625|handleMessageRefresh - <...>-625 (-----) [004] ...1 14594.813651: tracing_mark_write: B|625|preComposition - <...>-625 (-----) [004] ...1 14594.813693: tracing_mark_write: B|625|rebuildLayerStacks - <...>-625 (-----) [004] ...1 14594.813696: tracing_mark_write: B|625|rebuildLayerStacks VR Dirty - <...>-625 (-----) [004] ...1 14594.813701: tracing_mark_write: B|625|computeVisibleRegions - <...>-1398 (-----) [007] d..2 14594.813718: sched_switch: prev_comm=android.anim prev_pid=1398 prev_prio=116 prev_state=S ==> next_comm=system next_pid=108 next_prio=120 - <...>-108 (-----) [007] d..2 14594.813739: sched_switch: prev_comm=system prev_pid=108 prev_prio=120 prev_state=R+ ==> next_comm=android.anim next_pid=1398 next_prio=116 - <...>-1695 (-----) [002] d..2 14594.813970: sched_switch: prev_comm=InputDispatcher prev_pid=1695 prev_prio=112 prev_state=S ==> next_comm=system next_pid=108 next_prio=120 - <...>-1398 (-----) [007] ...1 14594.814029: tracing_mark_write: B|1368|wmLayout - <...>-1398 (-----) [007] ...1 14594.814033: tracing_mark_write: B|1368|performSurfacePlacement - <...>-1398 (-----) [007] ...1 14594.814040: tracing_mark_write: B|1368|applySurfaceChanges - <...>-1398 (-----) [007] ...1 14594.814043: tracing_mark_write: B|1368|openSurfaceTransaction - <...>-1398 (-----) [007] ...1 14594.814063: tracing_mark_write: B|1368|performLayout - <...>-625 (-----) [004] ...1 14594.814119: tracing_mark_write: B|625|calculateWorkingSet - <...>-1398 (-----) [007] ...1 14594.814241: tracing_mark_write: B|1368|layoutInputConsumer - <...>-2048 (-----) [000] ...1 14594.814260: tracing_mark_write: B|1803|prepareTree - <...>-1398 (-----) [007] ...1 14594.814263: tracing_mark_write: B|1368|applyPostLayoutPolicy - <...>-2048 (-----) [000] d..2 14594.814408: sched_switch: prev_comm=RenderThread prev_pid=2048 prev_prio=120 prev_state=R ==> next_comm=Binder:1368_15 next_pid=3359 next_prio=120 - <...>-625 (-----) [004] ...1 14594.814411: tracing_mark_write: B|625|prepare - <...>-625 (-----) [004] ...1 14594.814428: tracing_mark_write: B|625|HIDL::IComposerClient::executeCommands_2_2::client - <...>-2048 (-----) [000] d..2 14594.814533: sched_switch: prev_comm=RenderThread prev_pid=2048 prev_prio=120 prev_state=R+ ==> next_comm=ndroid.systemui next_pid=1803 next_prio=120 - <...>-1803 (-----) [000] d..2 14594.814558: sched_switch: prev_comm=ndroid.systemui prev_pid=1803 prev_prio=120 prev_state=S ==> next_comm=RenderThread next_pid=2048 next_prio=120 - <...>-2048 (-----) [000] d..2 14594.814572: sched_switch: prev_comm=RenderThread prev_pid=2048 prev_prio=120 prev_state=R+ ==> next_comm=ndroid.systemui next_pid=1803 next_prio=120 - <...>-625 (-----) [004] ...2 14594.814589: binder_set_priority: proc=627 thread=627 old=97 => new=98 desired=98 - <...>-108 (-----) [002] d..2 14594.814650: sched_switch: prev_comm=system prev_pid=108 prev_prio=120 prev_state=R+ ==> next_comm=composer@2.2-se next_pid=627 next_prio=98 - <...>-625 (-----) [004] d..2 14594.814664: sched_switch: prev_comm=surfaceflinger prev_pid=625 prev_prio=98 prev_state=S ==> next_comm=ashmemd next_pid=854 next_prio=129 - <...>-1398 (-----) [007] ...1 14594.814723: tracing_mark_write: B|1368|applyWindowSurfaceChanges - <...>-854 (-----) [004] .... 14594.814746: binder_set_priority: proc=854 thread=854 old=129 => new=120 desired=120 - <...>-854 (-----) [004] d..2 14594.814757: sched_switch: prev_comm=ashmemd prev_pid=854 prev_prio=120 prev_state=R+ ==> next_comm=highpool[0] next_pid=3493 next_prio=129 - <...>-1803 (-----) [000] d..2 14594.814763: sched_switch: prev_comm=ndroid.systemui prev_pid=1803 prev_prio=120 prev_state=S ==> next_comm=RenderThread next_pid=2048 next_prio=120 - <...>-18733 (-----) [001] d..1 14594.814819: mm_filemap_delete_from_page_cache: dev 0:1 ino 3ce5e7 page=0000000083f10c7a pfn=1298474 ofs=0 - <...>-2048 (-----) [000] ...1 14594.814842: tracing_mark_write: B|1803|dequeueBuffer - <...>-1398 (-----) [007] ...1 14594.814850: tracing_mark_write: F|1368|launching: com.google.android.dialer|0 - <...>-1398 (-----) [007] ...1 14594.814855: tracing_mark_write: B|1368|MetricsLogger:launchObserverNotifyActivityLaunchFinished - <...>-1398 (-----) [007] ...1 14594.814857: tracing_mark_write: B|1368|MetricsLogger:convertActivityRecordToProto - <...>-2048 (-----) [000] d..2 14594.814905: sched_switch: prev_comm=RenderThread prev_pid=2048 prev_prio=120 prev_state=R+ ==> next_comm=Binder:625_1 next_pid=656 next_prio=120 - <...>-1410 (-----) [006] .... 14592.997816: mm_filemap_add_to_page_cache: dev 253:6 ino b785 page=00000000615a8f24 pfn=1134764 ofs=0 - <...>-1410 (-----) [006] .... 14592.997831: mm_filemap_add_to_page_cache: dev 253:6 ino b785 page=000000008768a58f pfn=1134751 ofs=4096 - - <...>-18733 (-----) [001] .... 14594.814914: binder_set_priority: proc=10691 thread=18733 old=110 => new=120 desired=120 - <...>-14655 (-----) [006] d..2 14594.814932: sched_switch: prev_comm=DialerExecutors prev_pid=14655 prev_prio=130 prev_state=R ==> next_comm=.android.dialer next_pid=14607 next_prio=110 - <...>-656 (-----) [000] ...1 14594.814948: tracing_mark_write: B|625|dequeueBuffer - <...>-3514 (-----) [001] .... 14594.814954: binder_set_priority: proc=1368 thread=3514 old=110 => new=120 desired=120 - <...>-656 (-----) [000] ...1 14594.814963: tracing_mark_write: B|625|NavigationBar0#0: 2 - <...>-14607 (-----) [006] ...2 14594.815022: binder_set_priority: proc=1368 thread=3514 old=120 => new=110 desired=110 - <...>-1398 (-----) [007] ...1 14594.815039: tracing_mark_write: B|1368|prepareSurfaces - <...>-14607 (-----) [006] d..2 14594.815041: sched_switch: prev_comm=.android.dialer prev_pid=14607 prev_prio=110 prev_state=S ==> next_comm=DialerExecutors next_pid=14655 next_prio=130 - <...>-3493 (-----) [004] d..2 14594.815057: sched_switch: prev_comm=highpool[0] prev_pid=3493 prev_prio=129 prev_state=R ==> next_comm=Binder:1368_18 next_pid=3514 next_prio=110 - <...>-2048 (-----) [000] ...1 14594.815088: tracing_mark_write: B|1803|HWC release fence 45750 has signaled - <...>-2048 (-----) [000] ...1 14594.815119: tracing_mark_write: B|1803|eglBeginFrame - <...>-14655 (-----) [006] d..2 14594.815190: sched_switch: prev_comm=DialerExecutors prev_pid=14655 prev_prio=130 prev_state=R ==> next_comm=crtc_commit:97 next_pid=301 next_prio=83 - <...>-3514 (-----) [004] .... 14594.815193: binder_set_priority: proc=1368 thread=3514 old=110 => new=120 desired=120 - <...>-1398 (-----) [007] ...1 14594.815322: tracing_mark_write: B|1368|closeSurfaceTransaction - <...>-3493 (-----) [004] .... 14594.815353: mm_filemap_add_to_page_cache: dev 253:6 ino 113b page=0000000069e2b98a pfn=628464 ofs=2723840 - <...>-1398 (-----) [007] ...2 14594.815393: binder_set_priority: proc=625 thread=656 old=120 => new=116 desired=116 - rcu_sched-8 ( 8) [007] d..2 14594.815449: sched_switch: prev_comm=rcu_sched prev_pid=8 prev_prio=120 prev_state=S ==> next_comm=Binder:625_1 next_pid=656 next_prio=116 diff --git a/startop/scripts/trace_analyzer/trace_analyzer b/startop/scripts/trace_analyzer/trace_analyzer deleted file mode 100755 index 8c0396430c40..000000000000 --- a/startop/scripts/trace_analyzer/trace_analyzer +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -DIR="$( cd "$(dirname "$0")" ; pwd -P )" - -if [[ "$#" -lt 2 ]]; then - echo "Usage: $0 <filename.trace> <sqlite-filename.db>" >&2 - exit 1 -fi - -TRACE_FILENAME="$1" -SQLITE_FILENAME="$2" - -#echo "Trace filename: $TRACE_FILENAME" -#echo "SQLite filename: $SQLITE_FILENAME" - -if ! [[ -f "$TRACE_FILENAME" ]]; then - echo "Error: Trace '$TRACE_FILENAME' does not exist." >&2 - exit 1 -fi - -if ! "$DIR/trace_analyzer.py" "$SQLITE_FILENAME" "$TRACE_FILENAME" > /dev/null; then - echo "Fatal: trace_analyzer.py failed, aborting." >&2 - exit 1 -fi - -if ! "$DIR/run-sql-queries" "$SQLITE_FILENAME"; then - echo "Fatal: Failed to run sql queries, aborting." >&2 - exit 1 -fi diff --git a/startop/scripts/trace_analyzer/trace_analyzer.py b/startop/scripts/trace_analyzer/trace_analyzer.py deleted file mode 100755 index 62ae018a9986..000000000000 --- a/startop/scripts/trace_analyzer/trace_analyzer.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/python3 -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import re -import sys -import argparse - -from lib.trace2db import Trace2Db - -# This script requires 'sqlalchemy' to access the sqlite3 database. -# -# $> sudo apt-get install python3-pip -# $> pip3 install --user sqlalchemy -# - -def main(argv): - parser = argparse.ArgumentParser(description='Convert ftrace/systrace file into sqlite3 db.') - parser.add_argument('db_filename', metavar='sql_filename.db', type=str, - help='path to sqlite3 db filename') - parser.add_argument('trace_filename', metavar='systrace.ftrace', type=str, - help='path to ftrace/systrace filename') - parser.add_argument('--limit', type=int, help='limit the number of entries parsed [for debugging]') - - args = parser.parse_args() - - db_filename = args.db_filename - trace_filename = args.trace_filename - - trace2db = Trace2Db(db_filename) - print("SQL Alchemy db initialized") - - # parse 'raw_ftrace_entries' table - count = trace2db.parse_file_into_db(trace_filename, limit=args.limit) - print("Count was ", count) - - return 0 - -if __name__ == '__main__': - main(sys.argv) diff --git a/startop/scripts/trace_analyzer/trace_analyzer_recursive b/startop/scripts/trace_analyzer/trace_analyzer_recursive deleted file mode 100755 index 4d9ee0eec9b0..000000000000 --- a/startop/scripts/trace_analyzer/trace_analyzer_recursive +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash -# Copyright (C) 2019 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -DIR="$( cd "$(dirname "$0")" ; pwd -P )" - -if [[ "$#" -lt 3 ]]; then - echo "Usage: $0 <trace-dir> <db-dir> <output.csv>" >&2 - exit 1 -fi - -simulate="n" - -TRACE_DIRNAME="$1" -SQLITE_DIRNAME="$2" -OUTPUT_FILENAME="$3" - -echo "Trace filename: $TRACE_DIRNAME" -echo "SQLite filename: $SQLITE_DIRNAME" - -if ! [[ -d "$TRACE_DIRNAME" ]]; then - echo "Error: Trace '$TRACE_DIRNAME' does not exist." >&2 - exit 1 -fi - -process_trace_file() { - local trace_filename="$1" - local db_dirname="$2" - local output_file="$3" - - local db_filename="$db_dirname/$(basename "$trace_filename").db" - - if [[ $simulate == y ]]; then - echo "$DIR/trace_analyzer.py" "$db_filename" "$trace_filename" "> /dev/null" - else - if ! "$DIR/trace_analyzer.py" "$db_filename" "$trace_filename" > /dev/null; then - echo "Fatal: trace_analyzer.py failed, aborting." >&2 - return 1 - fi - fi - - if [[ $simulate == y ]]; then - echo "$DIR/run-sql-queries" "$db_filename" ">> '$output_file'" - else - # append name of trace to CSV, so we can see where data came from - echo "; $trace_filename" >> "$output_file" - if ! "$DIR/run-sql-queries" "$db_filename" >> "$output_file"; then - echo "Fatal: Failed to run sql queries, aborting." >&2 - return 1 - fi - fi - - return 0 -} - -find "$TRACE_DIRNAME" -type f -name '*.trace' -print0 | -while IFS= read -r -d '' file; do - if [[ $file == *#*.trace && $file != *#1.trace ]]; then - echo "Skip $file" - continue - fi - - printf '%s\n' "$file" - process_trace_file "$file" "$SQLITE_DIRNAME" "$OUTPUT_FILENAME" -done - -echo "Done" diff --git a/startop/scripts/trace_analyzer/trace_analyzer_test.py b/startop/scripts/trace_analyzer/trace_analyzer_test.py deleted file mode 100644 index 579529c6f6d5..000000000000 --- a/startop/scripts/trace_analyzer/trace_analyzer_test.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -# -# 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. -# - -""" -Unit tests for trace_analyzer module. - -Install: - $> sudo apt-get install python3-pytest ## OR - $> pip install -U pytest -See also https://docs.pytest.org/en/latest/getting-started.html - -Usage: - $> pytest trace_analyzer_test.py - -See also https://docs.pytest.org/en/latest/usage.html -""" - -# global imports -import os -import sys - -DIR = os.path.abspath(os.path.dirname(__file__)) - -sys.path.append(os.path.dirname(DIR)) -import lib.cmd_utils as cmd_utils - -def test_trace_analyzer(tmpdir): - # Setup - bin = os.path.join(DIR, 'trace_analyzer') - systrace = os.path.join(DIR, 'test_fixtures/common_systrace') - db_file = tmpdir.mkdir('trace_analyzer').join('test.db') - - # Act - passed, output = cmd_utils.execute_arbitrary_command([bin, systrace, - str(db_file)], - timeout=300, - shell=False, - simulate=False) - - # Assert - assert passed - assert output == """\ -'blocked_iowait_duration_ms',\ -'process_name',\ -'launching_duration_ms',\ -'launching_started_timestamp_ms',\ -'launching_finished_timestamp_ms' -81.697999999960302375,\ -'com.google.android.dialer',\ -594.99400000095192808,\ -14594219.85600000061,\ -14594814.85000000149""" 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 7ba4b11caea3..e0e791321819 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8898,8 +8898,8 @@ public class CarrierConfigManager { sDefaults.putStringArray( KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] { "capabilities=eims, retry_interval=1000, maximum_retries=20", - "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|" - + "2254, maximum_retries=0", // No retry for those causes + "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2252|" + + "2253|2254, maximum_retries=0", // No retry for those causes "capabilities=mms|supl|cbs, retry_interval=2000", "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|" + "5000|10000|15000|20000|40000|60000|120000|240000|" @@ -8920,7 +8920,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 |