diff --git a/Android.bp b/Android.bp index 557c3209248daaf7504ae788cd6d9a074198e99b..14a2bff8ad13c1fbbe4b72dd85fe709c3f988b19 100644 --- a/Android.bp +++ b/Android.bp @@ -264,6 +264,7 @@ filegroup { ":libcamera_client_aidl", ":libcamera_client_framework_aidl", ":libupdate_engine_aidl", + ":resourcemanager_aidl", ":storaged_aidl", ":vold_aidl", @@ -292,13 +293,13 @@ filegroup { java_library { name: "framework-updatable-stubs-module_libs_api", static_libs: [ - "framework-media-stubs-module_libs_api", - "framework-mediaprovider-stubs-module_libs_api", - "framework-permission-stubs-module_libs_api", - "framework-sdkextensions-stubs-module_libs_api", - "framework-statsd-stubs-module_libs_api", - "framework-tethering-stubs-module_libs_api", - "framework-wifi-stubs-module_libs_api", + "framework-media.stubs.module_lib", + "framework-mediaprovider.stubs.module_lib", + "framework-permission.stubs.module_lib", + "framework-sdkextensions.stubs.module_lib", + "framework-statsd.stubs.module_lib", + "framework-tethering.stubs.module_lib", + "framework-wifi.stubs.module_lib", ], sdk_version: "module_current", visibility: [":__pkg__"], @@ -533,6 +534,8 @@ java_library { static_libs: [ "exoplayer2-extractor", "android.hardware.wifi-V1.0-java-constants", + // Additional dependencies needed to build the ike API classes. + "ike-internals", ], apex_available: ["//apex_available:platform"], visibility: [ @@ -700,7 +703,6 @@ filegroup { "core/java/com/android/internal/util/TrafficStatsConstants.java", "core/java/com/android/internal/util/WakeupMessage.java", "core/java/com/android/internal/util/TokenBucket.java", - "core/java/android/net/shared/*.java", ], } @@ -708,7 +710,6 @@ filegroup { name: "framework-services-net-module-wifi-shared-srcs", srcs: [ "core/java/android/net/DhcpResults.java", - "core/java/android/net/shared/InetAddressUtils.java", "core/java/android/net/util/IpUtils.java", "core/java/android/util/LocalLog.java", ], @@ -725,7 +726,6 @@ filegroup { "core/java/com/android/internal/util/State.java", "core/java/com/android/internal/util/StateMachine.java", "core/java/com/android/internal/util/TrafficStatsConstants.java", - "core/java/android/net/shared/Inet4AddressUtils.java", ], } @@ -1162,7 +1162,6 @@ java_library { srcs: [ "core/java/android/content/pm/BaseParceledListSlice.java", "core/java/android/content/pm/ParceledListSlice.java", - "core/java/android/net/shared/Inet4AddressUtils.java", "core/java/android/os/HandlerExecutor.java", "core/java/com/android/internal/util/AsyncChannel.java", "core/java/com/android/internal/util/AsyncService.java", diff --git a/StubLibraries.bp b/StubLibraries.bp index c0197c4b2acfb36fa157c5b13a41f76662d6df72..ef4e202fdfe25f6752c192978647c0e600ee134c 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -329,13 +329,13 @@ java_library_static { srcs: [ ":api-stubs-docs-non-updatable" ], static_libs: [ "conscrypt.module.public.api.stubs", - "framework-media-stubs-publicapi", - "framework-mediaprovider-stubs-publicapi", - "framework-permission-stubs-publicapi", - "framework-sdkextensions-stubs-publicapi", - "framework-statsd-stubs-publicapi", - "framework-tethering-stubs-publicapi", - "framework-wifi-stubs-publicapi", + "framework-media.stubs", + "framework-mediaprovider.stubs", + "framework-permission.stubs", + "framework-sdkextensions.stubs", + "framework-statsd.stubs", + "framework-tethering.stubs", + "framework-wifi.stubs", "private-stub-annotations-jar", ], defaults: ["android_defaults_stubs_current"], @@ -359,13 +359,13 @@ java_library_static { srcs: [ ":system-api-stubs-docs-non-updatable" ], static_libs: [ "conscrypt.module.public.api.stubs", - "framework-media-stubs-systemapi", - "framework-mediaprovider-stubs-systemapi", - "framework-permission-stubs-systemapi", - "framework-sdkextensions-stubs-systemapi", - "framework-statsd-stubs-systemapi", - "framework-tethering-stubs-systemapi", - "framework-wifi-stubs-systemapi", + "framework-media.stubs.system", + "framework-mediaprovider.stubs.system", + "framework-permission.stubs.system", + "framework-sdkextensions.stubs.system", + "framework-statsd.stubs.system", + "framework-tethering.stubs.system", + "framework-wifi.stubs.system", "private-stub-annotations-jar", ], defaults: ["android_defaults_stubs_current"], diff --git a/apct-tests/perftests/multiuser/Android.bp b/apct-tests/perftests/multiuser/Android.bp index 508bf6007a9f75869e67b9677b39866aa1e64b9a..04432f25e33706b65fa74951cab654ac94ac0824 100644 --- a/apct-tests/perftests/multiuser/Android.bp +++ b/apct-tests/perftests/multiuser/Android.bp @@ -17,6 +17,7 @@ android_test { srcs: ["src/**/*.java"], static_libs: [ "androidx.test.rules", + "collector-device-lib-platform", "apct-perftests-utils", ], platform_apis: true, diff --git a/apct-tests/perftests/multiuser/AndroidManifest.xml b/apct-tests/perftests/multiuser/AndroidManifest.xml index 893c8ca9328ba145d15ed0d830e61860360d288a..e4196dd6cf12fdcf8396aac18067ff38ebf3f51b 100644 --- a/apct-tests/perftests/multiuser/AndroidManifest.xml +++ b/apct-tests/perftests/multiuser/AndroidManifest.xml @@ -17,12 +17,16 @@ + + + + diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java index 1667c1658a0704adca47d236ec86b1d2b4b11a05..6122ef254855fcb0858794583d0e20dd1171879e 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java @@ -23,6 +23,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.hamcrest.core.AnyOf.anyOf; import static org.hamcrest.core.Is.is; +import android.app.ActivityManager; import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; @@ -121,6 +122,12 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { @AfterClass public static void tearDownClass() { sSetUpClassException = null; + try { + // Recents activity may stop app switches. Restore the state to avoid affecting + // the next test. + ActivityManager.resumeAppSwitches(); + } catch (RemoteException ignored) { + } sUiAutomation.dropShellPermissionIdentity(); } diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java index 8139a2e963c561d1c169caa3c11b16211f48c03c..f04e5556752047f95d43debb4f1d36a680f8380f 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java @@ -88,10 +88,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { public void testRelayout() throws Throwable { final Activity activity = mActivityRule.getActivity(); final ContentView contentView = new ContentView(activity); - mActivityRule.runOnUiThread(() -> { - activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - activity.setContentView(contentView); - }); + mActivityRule.runOnUiThread(() -> activity.setContentView(contentView)); getInstrumentation().waitForIdleSync(); final RelayoutRunner relayoutRunner = new RelayoutRunner(activity, contentView.getWindow(), diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java index 9e17e940a06bf6a4cb1314f4ec325b5a81e2b06b..655d2f7f8aa767b896d1c7ce88b97864b4209e94 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java @@ -19,11 +19,13 @@ package android.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import android.app.Activity; +import android.app.KeyguardManager; import android.app.UiAutomation; import android.content.Context; import android.content.Intent; import android.os.BatteryManager; import android.os.ParcelFileDescriptor; +import android.os.PowerManager; import android.perftests.utils.PerfTestActivity; import android.provider.Settings; @@ -61,24 +63,32 @@ public class WindowManagerPerfTestBase { @BeforeClass public static void setUpOnce() { final Context context = getInstrumentation().getContext(); - sOriginalStayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(), + final int stayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(), Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); - // Keep the device awake during testing. - setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_USB); + sOriginalStayOnWhilePluggedIn = -1; + if (stayOnWhilePluggedIn != BatteryManager.BATTERY_PLUGGED_ANY) { + sOriginalStayOnWhilePluggedIn = stayOnWhilePluggedIn; + // Keep the device awake during testing. + setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_ANY); + } if (!BASE_OUT_PATH.exists()) { executeShellCommand("mkdir -p " + BASE_OUT_PATH); } - // In order to be closer to the real use case. - executeShellCommand("input keyevent KEYCODE_WAKEUP"); - executeShellCommand("wm dismiss-keyguard"); + if (!context.getSystemService(PowerManager.class).isInteractive() + || context.getSystemService(KeyguardManager.class).isKeyguardLocked()) { + executeShellCommand("input keyevent KEYCODE_WAKEUP"); + executeShellCommand("wm dismiss-keyguard"); + } context.startActivity(new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } @AfterClass public static void tearDownOnce() { - setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); + if (sOriginalStayOnWhilePluggedIn != -1) { + setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); + } } private static void setStayOnWhilePluggedIn(int value) { diff --git a/apex/Android.bp b/apex/Android.bp index c1715a002d6d19375e42cc74b1b0b33c9315f851..992648b04ef04c5f0d5110642c0a6e327c37759e 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -63,9 +63,9 @@ mainline_service_stubs_args = "--hide-annotation android.annotation.Hide " + "--hide InternalClasses " // com.android.* classes are okay in this interface -// Defaults for mainline module provided java_sdk_library instances. +// Defaults common to all mainline module java_sdk_library instances. java_defaults { - name: "framework-module-defaults", + name: "framework-module-common-defaults", // Additional annotations used for compiling both the implementation and the // stubs libraries. @@ -88,24 +88,14 @@ java_defaults { enabled: true, sdk_version: "module_current", }, - system: { - enabled: true, - sdk_version: "module_current", - }, - module_lib: { - enabled: true, - sdk_version: "module_current", - }, // Configure framework module specific metalava options. droiddoc_options: [mainline_stubs_args], annotations_enabled: true, - // The stub libraries must be visible to frameworks/base so they can be combined - // into API specific libraries. stubs_library_visibility: [ - "//frameworks/base", // Framework + "//visibility:public", ], // Set the visibility of the modules creating the stubs source. @@ -127,6 +117,32 @@ java_defaults { sdk_version: "module_current", } +// Defaults for mainline module provided java_sdk_library instances. +java_defaults { + name: "framework-module-defaults", + defaults: ["framework-module-common-defaults"], + + system: { + enabled: true, + sdk_version: "module_current", + }, + module_lib: { + enabled: true, + sdk_version: "module_current", + }, +} + +// Defaults for mainline module system server provided java_sdk_library instances. +java_defaults { + name: "framework-system-server-module-defaults", + defaults: ["framework-module-common-defaults"], + + system_server: { + enabled: true, + sdk_version: "module_current", + }, +} + stubs_defaults { name: "framework-module-stubs-defaults-publicapi", args: mainline_framework_stubs_args, diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java index bcef8ceaa941c60cce166785f96f4c3def141363..113f8fe9e248f793df3ec8bddd4338c53ec5030b 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java @@ -51,6 +51,7 @@ public final class BlobHandle implements Parcelable { }; private static final int LIMIT_BLOB_TAG_LENGTH = 128; // characters + private static final int LIMIT_BLOB_LABEL_LENGTH = 100; // characters /** * Cyrptographically secure hash algorithm used to generate hash of the blob this handle is @@ -128,6 +129,9 @@ public final class BlobHandle implements Parcelable { * * @param digest the SHA-256 hash of the blob this is representing. * @param label a label indicating what the blob is, that can be surfaced to the user. + * The length of the label cannot be more than 100 characters. It is recommended + * to keep this brief. This may be truncated and ellipsized if it is too long + * to be displayed to the user. * @param expiryTimeMillis the time in secs after which the blob should be invalidated and not * allowed to be accessed by any other app, * in {@link System#currentTimeMillis()} timebase or {@code 0} to @@ -205,9 +209,9 @@ public final class BlobHandle implements Parcelable { final BlobHandle other = (BlobHandle) obj; return this.algorithm.equals(other.algorithm) && Arrays.equals(this.digest, other.digest) - && this.label.equals(other.label) + && this.label.toString().equals(other.label.toString()) && this.expiryTimeMillis == other.expiryTimeMillis - && this.tag.equals(tag); + && this.tag.equals(other.tag); } @Override @@ -219,7 +223,7 @@ public final class BlobHandle implements Parcelable { public void dump(IndentingPrintWriter fout, boolean dumpFull) { if (dumpFull) { fout.println("algo: " + algorithm); - fout.println("digest: " + (dumpFull ? encodeDigest() : safeDigest())); + fout.println("digest: " + (dumpFull ? encodeDigest(digest) : safeDigest(digest))); fout.println("label: " + label); fout.println("expiryMs: " + expiryTimeMillis); fout.println("tag: " + tag); @@ -233,6 +237,7 @@ public final class BlobHandle implements Parcelable { Preconditions.checkArgumentIsSupported(SUPPORTED_ALGOS, algorithm); Preconditions.checkByteArrayNotEmpty(digest, "digest"); Preconditions.checkStringNotEmpty(label, "label must not be null"); + Preconditions.checkArgument(label.length() <= LIMIT_BLOB_LABEL_LENGTH, "label too long"); Preconditions.checkArgumentNonnegative(expiryTimeMillis, "expiryTimeMillis must not be negative"); Preconditions.checkStringNotEmpty(tag, "tag must not be null"); @@ -243,19 +248,20 @@ public final class BlobHandle implements Parcelable { public String toString() { return "BlobHandle {" + "algo:" + algorithm + "," - + "digest:" + safeDigest() + "," + + "digest:" + safeDigest(digest) + "," + "label:" + label + "," + "expiryMs:" + expiryTimeMillis + "," + "tag:" + tag + "}"; } - private String safeDigest() { - final String digestStr = encodeDigest(); + /** @hide */ + public static String safeDigest(@NonNull byte[] digest) { + final String digestStr = encodeDigest(digest); return digestStr.substring(0, 2) + ".." + digestStr.substring(digestStr.length() - 2); } - private String encodeDigest() { + private static String encodeDigest(@NonNull byte[] digest) { return Base64.encodeToString(digest, Base64.NO_WRAP); } diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java index 80062d5d245f825bcf8dc5fe8e108d31d3569dbb..ba92d95b483edd15767427eed2d3444fbc381f2c 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java @@ -16,9 +16,13 @@ package android.app.blob; +import static android.text.format.Formatter.FLAG_IEC_UNITS; + import android.annotation.NonNull; +import android.app.AppGlobals; import android.os.Parcel; import android.os.Parcelable; +import android.text.format.Formatter; import java.util.Collections; import java.util.List; @@ -32,13 +36,15 @@ public final class BlobInfo implements Parcelable { private final long mId; private final long mExpiryTimeMs; private final CharSequence mLabel; + private final long mSizeBytes; private final List mLeaseInfos; - public BlobInfo(long id, long expiryTimeMs, CharSequence label, + public BlobInfo(long id, long expiryTimeMs, CharSequence label, long sizeBytes, List leaseInfos) { mId = id; mExpiryTimeMs = expiryTimeMs; mLabel = label; + mSizeBytes = sizeBytes; mLeaseInfos = leaseInfos; } @@ -46,6 +52,7 @@ public final class BlobInfo implements Parcelable { mId = in.readLong(); mExpiryTimeMs = in.readLong(); mLabel = in.readCharSequence(); + mSizeBytes = in.readLong(); mLeaseInfos = in.readArrayList(null /* classloader */); } @@ -61,6 +68,10 @@ public final class BlobInfo implements Parcelable { return mLabel; } + public long getSizeBytes() { + return mSizeBytes; + } + public List getLeases() { return Collections.unmodifiableList(mLeaseInfos); } @@ -70,6 +81,7 @@ public final class BlobInfo implements Parcelable { dest.writeLong(mId); dest.writeLong(mExpiryTimeMs); dest.writeCharSequence(mLabel); + dest.writeLong(mSizeBytes); dest.writeList(mLeaseInfos); } @@ -83,10 +95,16 @@ public final class BlobInfo implements Parcelable { + "id: " + mId + "," + "expiryMs: " + mExpiryTimeMs + "," + "label: " + mLabel + "," + + "size: " + formatBlobSize(mSizeBytes) + "," + "leases: " + LeaseInfo.toShortString(mLeaseInfos) + "," + "}"; } + private static String formatBlobSize(long sizeBytes) { + return Formatter.formatFileSize(AppGlobals.getInitialApplication(), + sizeBytes, FLAG_IEC_UNITS); + } + @Override public int describeContents() { return 0; diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index 483d2cc2ec57aca719af81acebfc93dd82b08e3a..39f7526560a9ae9e8fe8087f437f3c44243f5632 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -184,9 +184,8 @@ public class BlobStoreManager { * @throws SecurityException when the caller is not allowed to create a session, such * as when called from an Instant app. * @throws IllegalArgumentException when {@code blobHandle} is invalid. - * @throws IllegalStateException when a new session could not be created, such as when the - * caller is trying to create too many sessions or when the - * device is running low on space. + * @throws LimitExceededException when a new session could not be created, such as when the + * caller is trying to create too many sessions. */ public @IntRange(from = 1) long createSession(@NonNull BlobHandle blobHandle) throws IOException { @@ -194,6 +193,7 @@ public class BlobStoreManager { return mService.createSession(blobHandle, mContext.getOpPackageName()); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); + e.maybeRethrow(LimitExceededException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -302,8 +302,9 @@ public class BlobStoreManager { * if the {@code leaseExpiryTimeMillis} is greater than the * {@link BlobHandle#getExpiryTimeMillis()}. * @throws LimitExceededException when a lease could not be acquired, such as when the - * caller is trying to acquire leases on too much data. Apps - * can avoid this by checking the remaining quota using + * caller is trying to acquire too many leases or acquire + * leases on too much data. Apps can avoid this by checking + * the remaining quota using * {@link #getRemainingLeaseQuotaBytes()} before trying to * acquire a lease. * @@ -346,7 +347,9 @@ public class BlobStoreManager { * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to * acquire a lease for. * @param description a short description string that can be surfaced - * to the user explaining what the blob is used for. + * to the user explaining what the blob is used for. It is recommended to + * keep this description brief. This may be truncated and ellipsized + * if it is too long to be displayed to the user. * @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be * automatically released, in {@link System#currentTimeMillis()} * timebase. If its value is {@code 0}, then the behavior of this @@ -362,8 +365,9 @@ public class BlobStoreManager { * if the {@code leaseExpiryTimeMillis} is greater than the * {@link BlobHandle#getExpiryTimeMillis()}. * @throws LimitExceededException when a lease could not be acquired, such as when the - * caller is trying to acquire leases on too much data. Apps - * can avoid this by checking the remaining quota using + * caller is trying to acquire too many leases or acquire + * leases on too much data. Apps can avoid this by checking + * the remaining quota using * {@link #getRemainingLeaseQuotaBytes()} before trying to * acquire a lease. * @@ -415,8 +419,9 @@ public class BlobStoreManager { * exist or the caller does not have access to it. * @throws IllegalArgumentException when {@code blobHandle} is invalid. * @throws LimitExceededException when a lease could not be acquired, such as when the - * caller is trying to acquire leases on too much data. Apps - * can avoid this by checking the remaining quota using + * caller is trying to acquire too many leases or acquire + * leases on too much data. Apps can avoid this by checking + * the remaining quota using * {@link #getRemainingLeaseQuotaBytes()} before trying to * acquire a lease. * @@ -455,15 +460,18 @@ public class BlobStoreManager { * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to * acquire a lease for. * @param description a short description string that can be surfaced - * to the user explaining what the blob is used for. + * to the user explaining what the blob is used for. It is recommended to + * keep this description brief. This may be truncated and + * ellipsized if it is too long to be displayed to the user. * * @throws IOException when there is an I/O error while acquiring a lease to the blob. * @throws SecurityException when the blob represented by the {@code blobHandle} does not * exist or the caller does not have access to it. * @throws IllegalArgumentException when {@code blobHandle} is invalid. * @throws LimitExceededException when a lease could not be acquired, such as when the - * caller is trying to acquire leases on too much data. Apps - * can avoid this by checking the remaining quota using + * caller is trying to acquire too many leases or acquire + * leases on too much data. Apps can avoid this by checking + * the remaining quota using * {@link #getRemainingLeaseQuotaBytes()} before trying to * acquire a lease. * @@ -757,6 +765,8 @@ public class BlobStoreManager { * @throws SecurityException when the caller is not the owner of the session. * @throws IllegalStateException when the caller tries to change access for a blob which is * already committed. + * @throws LimitExceededException when the caller tries to explicitly allow too + * many packages using this API. */ public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) throws IOException { @@ -764,6 +774,7 @@ public class BlobStoreManager { mSession.allowPackageAccess(packageName, certificate); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); + e.maybeRethrow(LimitExceededException.class); throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java index ec7ba287acec78a73ca7a21d2756caf224911c46..ba0fab6b4bc525839221d8a67d23a8fb43742d16 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java @@ -44,7 +44,7 @@ import java.util.Objects; /** * Class for representing how a blob can be shared. * - * Note that this class is not thread-safe, callers need to take of synchronizing access. + * Note that this class is not thread-safe, callers need to take care of synchronizing access. */ class BlobAccessMode { @Retention(RetentionPolicy.SOURCE) @@ -127,6 +127,14 @@ class BlobAccessMode { return false; } + int getAccessType() { + return mAccessType; + } + + int getNumWhitelistedPackages() { + return mWhitelistedPackages.size(); + } + void dump(IndentingPrintWriter fout) { fout.println("accessType: " + DebugUtils.flagsToString( BlobAccessMode.class, "ACCESS_TYPE_", mAccessType)); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java index cea7fcca6e2084abd330c6ba0c19e8d1d3d699d0..0b760a621d22808e15cb3a21d4e697cfb3419321 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -29,6 +29,8 @@ import static android.app.blob.XmlTags.TAG_COMMITTER; import static android.app.blob.XmlTags.TAG_LEASEE; import static android.os.Process.INVALID_UID; import static android.system.OsConstants.O_RDONLY; +import static android.text.format.Formatter.FLAG_IEC_UNITS; +import static android.text.format.Formatter.formatFileSize; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME; @@ -54,6 +56,8 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; +import android.util.StatsEvent; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -61,6 +65,8 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import com.android.server.blob.BlobStoreManagerService.DumpArgs; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -148,10 +154,10 @@ class BlobMetadata { } } - void removeInvalidCommitters(SparseArray packages) { + void removeCommittersFromUnknownPkgs(SparseArray knownPackages) { synchronized (mMetadataLock) { mCommitters.removeIf(committer -> - !committer.packageName.equals(packages.get(committer.uid))); + !committer.packageName.equals(knownPackages.get(committer.uid))); } } @@ -194,16 +200,27 @@ class BlobMetadata { } } - void removeInvalidLeasees(SparseArray packages) { + void removeLeaseesFromUnknownPkgs(SparseArray knownPackages) { synchronized (mMetadataLock) { mLeasees.removeIf(leasee -> - !leasee.packageName.equals(packages.get(leasee.uid))); + !leasee.packageName.equals(knownPackages.get(leasee.uid))); + } + } + + void removeExpiredLeases() { + synchronized (mMetadataLock) { + mLeasees.removeIf(leasee -> !leasee.isStillValid()); } } - boolean hasLeases() { + boolean hasValidLeases() { synchronized (mMetadataLock) { - return !mLeasees.isEmpty(); + for (int i = 0, size = mLeasees.size(); i < size; ++i) { + if (mLeasees.valueAt(i).isStillValid()) { + return true; + } + } + return false; } } @@ -220,8 +237,7 @@ class BlobMetadata { // Check if packageName already holds a lease on the blob. for (int i = 0, size = mLeasees.size(); i < size; ++i) { final Leasee leasee = mLeasees.valueAt(i); - if (leasee.equals(callingPackage, callingUid) - && leasee.isStillValid()) { + if (leasee.isStillValid() && leasee.equals(callingPackage, callingUid)) { return true; } } @@ -253,25 +269,32 @@ class BlobMetadata { boolean isALeasee(@Nullable String packageName, int uid) { synchronized (mMetadataLock) { - return isAnAccessor(mLeasees, packageName, uid); + final Leasee leasee = getAccessor(mLeasees, packageName, uid); + return leasee != null && leasee.isStillValid(); } } private static boolean isAnAccessor(@NonNull ArraySet accessors, @Nullable String packageName, int uid) { // Check if the package is an accessor of the data blob. + return getAccessor(accessors, packageName, uid) != null; + } + + private static T getAccessor(@NonNull ArraySet accessors, + @Nullable String packageName, int uid) { + // Check if the package is an accessor of the data blob. for (int i = 0, size = accessors.size(); i < size; ++i) { final Accessor accessor = accessors.valueAt(i); if (packageName != null && uid != INVALID_UID && accessor.equals(packageName, uid)) { - return true; + return (T) accessor; } else if (packageName != null && accessor.packageName.equals(packageName)) { - return true; + return (T) accessor; } else if (uid != INVALID_UID && accessor.uid == uid) { - return true; + return (T) accessor; } } - return false; + return null; } boolean isALeasee(@NonNull String packageName) { @@ -292,11 +315,11 @@ class BlobMetadata { private boolean hasOtherLeasees(@Nullable String packageName, int uid) { synchronized (mMetadataLock) { - if (mCommitters.size() > 1 || mLeasees.size() > 1) { - return true; - } for (int i = 0, size = mLeasees.size(); i < size; ++i) { final Leasee leasee = mLeasees.valueAt(i); + if (!leasee.isStillValid()) { + continue; + } // TODO: Also exclude packages which are signed with same cert? if (packageName != null && uid != INVALID_UID && !leasee.equals(packageName, uid)) { @@ -316,6 +339,9 @@ class BlobMetadata { synchronized (mMetadataLock) { for (int i = 0, size = mLeasees.size(); i < size; ++i) { final Leasee leasee = mLeasees.valueAt(i); + if (!leasee.isStillValid()) { + continue; + } if (leasee.uid == uid && leasee.packageName.equals(packageName)) { final int descriptionResId = leasee.descriptionResEntryName == null ? Resources.ID_NULL @@ -331,7 +357,9 @@ class BlobMetadata { } void forEachLeasee(Consumer consumer) { - mLeasees.forEach(consumer); + synchronized (mMetadataLock) { + mLeasees.forEach(consumer); + } } File getBlobFile() { @@ -349,14 +377,20 @@ class BlobMetadata { } catch (ErrnoException e) { throw e.rethrowAsIOException(); } - synchronized (mMetadataLock) { - return createRevocableFdLocked(fd, callingPackage); + try { + if (BlobStoreConfig.shouldUseRevocableFdForReads()) { + return createRevocableFd(fd, callingPackage); + } else { + return new ParcelFileDescriptor(fd); + } + } catch (IOException e) { + IoUtils.closeQuietly(fd); + throw e; } } - @GuardedBy("mMetadataLock") @NonNull - private ParcelFileDescriptor createRevocableFdLocked(FileDescriptor fd, + private ParcelFileDescriptor createRevocableFd(FileDescriptor fd, String callingPackage) throws IOException { final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); @@ -384,6 +418,26 @@ class BlobMetadata { return revocableFd.getRevocableFileDescriptor(); } + void destroy() { + revokeAllFds(); + getBlobFile().delete(); + } + + private void revokeAllFds() { + synchronized (mRevocableFds) { + for (int i = 0, pkgCount = mRevocableFds.size(); i < pkgCount; ++i) { + final ArraySet packageFds = + mRevocableFds.valueAt(i); + if (packageFds == null) { + continue; + } + for (int j = 0, fdCount = packageFds.size(); j < fdCount; ++j) { + packageFds.valueAt(j).revoke(); + } + } + } + } + boolean shouldBeDeleted(boolean respectLeaseWaitTime) { // Expired data blobs if (getBlobHandle().isExpired()) { @@ -392,7 +446,7 @@ class BlobMetadata { // Blobs with no active leases if ((!respectLeaseWaitTime || hasLeaseWaitTimeElapsedForAll()) - && !hasLeases()) { + && !hasValidLeases()) { return true; } @@ -410,55 +464,101 @@ class BlobMetadata { return true; } - void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { - fout.println("blobHandle:"); - fout.increaseIndent(); - mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); - fout.decreaseIndent(); - - fout.println("Committers:"); - fout.increaseIndent(); - if (mCommitters.isEmpty()) { - fout.println(""); - } else { - for (int i = 0, count = mCommitters.size(); i < count; ++i) { + StatsEvent dumpAsStatsEvent(int atomTag) { + synchronized (mMetadataLock) { + ProtoOutputStream proto = new ProtoOutputStream(); + // Write Committer data to proto format + for (int i = 0, size = mCommitters.size(); i < size; ++i) { final Committer committer = mCommitters.valueAt(i); - fout.println("committer " + committer.toString()); - fout.increaseIndent(); - committer.dump(fout); - fout.decreaseIndent(); + final long token = proto.start( + BlobStatsEventProto.BlobCommitterListProto.COMMITTER); + proto.write(BlobStatsEventProto.BlobCommitterProto.UID, committer.uid); + proto.write(BlobStatsEventProto.BlobCommitterProto.COMMIT_TIMESTAMP_MILLIS, + committer.commitTimeMs); + proto.write(BlobStatsEventProto.BlobCommitterProto.ACCESS_MODE, + committer.blobAccessMode.getAccessType()); + proto.write(BlobStatsEventProto.BlobCommitterProto.NUM_WHITELISTED_PACKAGE, + committer.blobAccessMode.getNumWhitelistedPackages()); + proto.end(token); } - } - fout.decreaseIndent(); + final byte[] committersBytes = proto.getBytes(); - fout.println("Leasees:"); - fout.increaseIndent(); - if (mLeasees.isEmpty()) { - fout.println(""); - } else { - for (int i = 0, count = mLeasees.size(); i < count; ++i) { + proto = new ProtoOutputStream(); + // Write Leasee data to proto format + for (int i = 0, size = mLeasees.size(); i < size; ++i) { final Leasee leasee = mLeasees.valueAt(i); - fout.println("leasee " + leasee.toString()); - fout.increaseIndent(); - leasee.dump(mContext, fout); - fout.decreaseIndent(); + final long token = proto.start(BlobStatsEventProto.BlobLeaseeListProto.LEASEE); + proto.write(BlobStatsEventProto.BlobLeaseeProto.UID, leasee.uid); + proto.write(BlobStatsEventProto.BlobLeaseeProto.LEASE_EXPIRY_TIMESTAMP_MILLIS, + leasee.expiryTimeMillis); + proto.end(token); } + final byte[] leaseesBytes = proto.getBytes(); + + // Construct the StatsEvent to represent this Blob + return StatsEvent.newBuilder() + .setAtomId(atomTag) + .writeLong(mBlobId) + .writeLong(getSize()) + .writeLong(mBlobHandle.getExpiryTimeMillis()) + .writeByteArray(committersBytes) + .writeByteArray(leaseesBytes) + .build(); } - fout.decreaseIndent(); + } - fout.println("Open fds:"); - fout.increaseIndent(); - if (mRevocableFds.isEmpty()) { - fout.println(""); - } else { - for (int i = 0, count = mRevocableFds.size(); i < count; ++i) { - final String packageName = mRevocableFds.keyAt(i); - final ArraySet packageFds = - mRevocableFds.valueAt(i); - fout.println(packageName + "#" + packageFds.size()); + void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { + synchronized (mMetadataLock) { + fout.println("blobHandle:"); + fout.increaseIndent(); + mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); + fout.decreaseIndent(); + fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); + + fout.println("Committers:"); + fout.increaseIndent(); + if (mCommitters.isEmpty()) { + fout.println(""); + } else { + for (int i = 0, count = mCommitters.size(); i < count; ++i) { + final Committer committer = mCommitters.valueAt(i); + fout.println("committer " + committer.toString()); + fout.increaseIndent(); + committer.dump(fout); + fout.decreaseIndent(); + } + } + fout.decreaseIndent(); + + fout.println("Leasees:"); + fout.increaseIndent(); + if (mLeasees.isEmpty()) { + fout.println(""); + } else { + for (int i = 0, count = mLeasees.size(); i < count; ++i) { + final Leasee leasee = mLeasees.valueAt(i); + fout.println("leasee " + leasee.toString()); + fout.increaseIndent(); + leasee.dump(mContext, fout); + fout.decreaseIndent(); + } } + fout.decreaseIndent(); + + fout.println("Open fds:"); + fout.increaseIndent(); + if (mRevocableFds.isEmpty()) { + fout.println(""); + } else { + for (int i = 0, count = mRevocableFds.size(); i < count; ++i) { + final String packageName = mRevocableFds.keyAt(i); + final ArraySet packageFds = + mRevocableFds.valueAt(i); + fout.println(packageName + "#" + packageFds.size()); + } + } + fout.decreaseIndent(); } - fout.decreaseIndent(); } void writeToXml(XmlSerializer out) throws IOException { @@ -635,7 +735,7 @@ class BlobMetadata { } boolean isStillValid() { - return expiryTimeMillis == 0 || expiryTimeMillis <= System.currentTimeMillis(); + return expiryTimeMillis == 0 || expiryTimeMillis >= System.currentTimeMillis(); } void dump(@NonNull Context context, @NonNull IndentingPrintWriter fout) { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index 656726622cfda3ad5069c00d5f30e9236781fb1c..bb9f13f1712c5c7d2430dbacab9dc221ffac5df5 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -25,6 +25,7 @@ import android.content.Context; import android.os.Environment; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; +import android.text.TextUtils; import android.util.DataUnit; import android.util.Log; import android.util.Slog; @@ -49,6 +50,9 @@ class BlobStoreConfig { public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_SESSION_CREATION_TIME; + public static final long INVALID_BLOB_ID = 0; + public static final long INVALID_BLOB_SIZE = 0; + private static final String ROOT_DIR_NAME = "blobstore"; private static final String BLOBS_DIR_NAME = "blobs"; private static final String SESSIONS_INDEX_FILE_NAME = "sessions_index.xml"; @@ -119,6 +123,62 @@ class BlobStoreConfig { public static long COMMIT_COOL_OFF_DURATION_MS = DEFAULT_COMMIT_COOL_OFF_DURATION_MS; + /** + * Denotes whether to use RevocableFileDescriptor when apps try to read session/blob data. + */ + public static final String KEY_USE_REVOCABLE_FD_FOR_READS = + "use_revocable_fd_for_reads"; + public static final boolean DEFAULT_USE_REVOCABLE_FD_FOR_READS = true; + public static boolean USE_REVOCABLE_FD_FOR_READS = + DEFAULT_USE_REVOCABLE_FD_FOR_READS; + + /** + * Denotes how long before a blob is deleted, once the last lease on it is released. + */ + public static final String KEY_DELETE_ON_LAST_LEASE_DELAY_MS = + "delete_on_last_lease_delay_ms"; + public static final long DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS = + TimeUnit.HOURS.toMillis(6); + public static long DELETE_ON_LAST_LEASE_DELAY_MS = + DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS; + + /** + * Denotes the maximum number of active sessions per app at any time. + */ + public static final String KEY_MAX_ACTIVE_SESSIONS = "max_active_sessions"; + public static int DEFAULT_MAX_ACTIVE_SESSIONS = 250; + public static int MAX_ACTIVE_SESSIONS = DEFAULT_MAX_ACTIVE_SESSIONS; + + /** + * Denotes the maximum number of committed blobs per app at any time. + */ + public static final String KEY_MAX_COMMITTED_BLOBS = "max_committed_blobs"; + public static int DEFAULT_MAX_COMMITTED_BLOBS = 1000; + public static int MAX_COMMITTED_BLOBS = DEFAULT_MAX_COMMITTED_BLOBS; + + /** + * Denotes the maximum number of leased blobs per app at any time. + */ + public static final String KEY_MAX_LEASED_BLOBS = "max_leased_blobs"; + public static int DEFAULT_MAX_LEASED_BLOBS = 500; + public static int MAX_LEASED_BLOBS = DEFAULT_MAX_LEASED_BLOBS; + + /** + * Denotes the maximum number of packages explicitly permitted to access a blob + * (permitted as part of creating a {@link BlobAccessMode}). + */ + public static final String KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = "max_permitted_pks"; + public static int DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES = 300; + public static int MAX_BLOB_ACCESS_PERMITTED_PACKAGES = + DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES; + + /** + * Denotes the maximum number of characters that a lease description can have. + */ + public static final String KEY_LEASE_DESC_CHAR_LIMIT = "lease_desc_char_limit"; + public static int DEFAULT_LEASE_DESC_CHAR_LIMIT = 300; + public static int LEASE_DESC_CHAR_LIMIT = DEFAULT_LEASE_DESC_CHAR_LIMIT; + static void refresh(Properties properties) { if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) { return; @@ -148,6 +208,31 @@ class BlobStoreConfig { COMMIT_COOL_OFF_DURATION_MS = properties.getLong(key, DEFAULT_COMMIT_COOL_OFF_DURATION_MS); break; + case KEY_USE_REVOCABLE_FD_FOR_READS: + USE_REVOCABLE_FD_FOR_READS = properties.getBoolean(key, + DEFAULT_USE_REVOCABLE_FD_FOR_READS); + break; + case KEY_DELETE_ON_LAST_LEASE_DELAY_MS: + DELETE_ON_LAST_LEASE_DELAY_MS = properties.getLong(key, + DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS); + break; + case KEY_MAX_ACTIVE_SESSIONS: + MAX_ACTIVE_SESSIONS = properties.getInt(key, DEFAULT_MAX_ACTIVE_SESSIONS); + break; + case KEY_MAX_COMMITTED_BLOBS: + MAX_COMMITTED_BLOBS = properties.getInt(key, DEFAULT_MAX_COMMITTED_BLOBS); + break; + case KEY_MAX_LEASED_BLOBS: + MAX_LEASED_BLOBS = properties.getInt(key, DEFAULT_MAX_LEASED_BLOBS); + break; + case KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES: + MAX_BLOB_ACCESS_PERMITTED_PACKAGES = properties.getInt(key, + DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES); + break; + case KEY_LEASE_DESC_CHAR_LIMIT: + LEASE_DESC_CHAR_LIMIT = properties.getInt(key, + DEFAULT_LEASE_DESC_CHAR_LIMIT); + break; default: Slog.wtf(TAG, "Unknown key in device config properties: " + key); } @@ -175,6 +260,22 @@ class BlobStoreConfig { fout.println(String.format(dumpFormat, KEY_COMMIT_COOL_OFF_DURATION_MS, TimeUtils.formatDuration(COMMIT_COOL_OFF_DURATION_MS), TimeUtils.formatDuration(DEFAULT_COMMIT_COOL_OFF_DURATION_MS))); + fout.println(String.format(dumpFormat, KEY_USE_REVOCABLE_FD_FOR_READS, + USE_REVOCABLE_FD_FOR_READS, DEFAULT_USE_REVOCABLE_FD_FOR_READS)); + fout.println(String.format(dumpFormat, KEY_DELETE_ON_LAST_LEASE_DELAY_MS, + TimeUtils.formatDuration(DELETE_ON_LAST_LEASE_DELAY_MS), + TimeUtils.formatDuration(DEFAULT_DELETE_ON_LAST_LEASE_DELAY_MS))); + fout.println(String.format(dumpFormat, KEY_MAX_ACTIVE_SESSIONS, + MAX_ACTIVE_SESSIONS, DEFAULT_MAX_ACTIVE_SESSIONS)); + fout.println(String.format(dumpFormat, KEY_MAX_COMMITTED_BLOBS, + MAX_COMMITTED_BLOBS, DEFAULT_MAX_COMMITTED_BLOBS)); + fout.println(String.format(dumpFormat, KEY_MAX_LEASED_BLOBS, + MAX_LEASED_BLOBS, DEFAULT_MAX_LEASED_BLOBS)); + fout.println(String.format(dumpFormat, KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES, + MAX_BLOB_ACCESS_PERMITTED_PACKAGES, + DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES)); + fout.println(String.format(dumpFormat, KEY_LEASE_DESC_CHAR_LIMIT, + LEASE_DESC_CHAR_LIMIT, DEFAULT_LEASE_DESC_CHAR_LIMIT)); } } @@ -239,6 +340,60 @@ class BlobStoreConfig { < System.currentTimeMillis(); } + /** + * Return whether to use RevocableFileDescriptor when apps try to read session/blob data. + */ + public static boolean shouldUseRevocableFdForReads() { + return DeviceConfigProperties.USE_REVOCABLE_FD_FOR_READS; + } + + /** + * Returns the duration to wait before a blob is deleted, once the last lease on it is released. + */ + public static long getDeletionOnLastLeaseDelayMs() { + return DeviceConfigProperties.DELETE_ON_LAST_LEASE_DELAY_MS; + } + + /** + * Returns the maximum number of active sessions per app. + */ + public static int getMaxActiveSessions() { + return DeviceConfigProperties.MAX_ACTIVE_SESSIONS; + } + + /** + * Returns the maximum number of committed blobs per app. + */ + public static int getMaxCommittedBlobs() { + return DeviceConfigProperties.MAX_COMMITTED_BLOBS; + } + + /** + * Returns the maximum number of leased blobs per app. + */ + public static int getMaxLeasedBlobs() { + return DeviceConfigProperties.MAX_LEASED_BLOBS; + } + + /** + * Returns the maximum number of packages explicitly permitted to access a blob. + */ + public static int getMaxPermittedPackages() { + return DeviceConfigProperties.MAX_BLOB_ACCESS_PERMITTED_PACKAGES; + } + + /** + * Returns the lease description truncated to + * {@link DeviceConfigProperties#LEASE_DESC_CHAR_LIMIT} characters. + */ + public static CharSequence getTruncatedLeaseDescription(CharSequence description) { + if (TextUtils.isEmpty(description)) { + return description; + } + return TextUtils.trimToLengthWithEllipsis(description, + DeviceConfigProperties.LEASE_DESC_CHAR_LIMIT); + } + @Nullable public static File prepareBlobFile(long sessionId) { final File blobsDir = prepareBlobsDir(); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 8fff0d9c950b4c73a2bb4835d45eaf7ae7bb8290..d37dfdeaa58378ec0d116bbd2c2eedb38df34d9f 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -28,10 +28,16 @@ import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.os.UserHandle.USER_CURRENT; import static android.os.UserHandle.USER_NULL; +import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_ID; +import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE; import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT; import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs; +import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs; +import static com.android.server.blob.BlobStoreConfig.getMaxActiveSessions; +import static com.android.server.blob.BlobStoreConfig.getMaxCommittedBlobs; +import static com.android.server.blob.BlobStoreConfig.getMaxLeasedBlobs; import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED; import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID; @@ -48,6 +54,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.StatsManager; import android.app.blob.BlobHandle; import android.app.blob.BlobInfo; import android.app.blob.IBlobStoreManager; @@ -80,6 +87,7 @@ import android.util.ExceptionUtils; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; +import android.util.StatsEvent; import android.util.Xml; import com.android.internal.annotations.GuardedBy; @@ -88,6 +96,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; @@ -118,6 +127,7 @@ import java.util.List; import java.util.Objects; import java.util.Random; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Function; @@ -159,6 +169,8 @@ public class BlobStoreManagerService extends SystemService { new SessionStateChangeListener(); private PackageManagerInternal mPackageManagerInternal; + private StatsManager mStatsManager; + private StatsPullAtomCallbackImpl mStatsCallbackImpl = new StatsPullAtomCallbackImpl(); private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo; private final Runnable mSaveSessionsRunnable = this::writeBlobSessions; @@ -192,6 +204,7 @@ public class BlobStoreManagerService extends SystemService { LocalServices.addService(BlobStoreManagerInternal.class, new LocalService()); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + mStatsManager = getContext().getSystemService(StatsManager.class); registerReceivers(); LocalServices.getService(StorageStatsManagerInternal.class) .registerStorageStatsAugmenter(new BlobStorageStatsAugmenter(), TAG); @@ -207,6 +220,7 @@ public class BlobStoreManagerService extends SystemService { readBlobSessionsLocked(allPackages); readBlobsInfoLocked(allPackages); } + registerBlobStorePuller(); } else if (phase == PHASE_BOOT_COMPLETED) { BlobStoreIdleJobService.schedule(mContext); } @@ -218,8 +232,9 @@ public class BlobStoreManagerService extends SystemService { int n = 0; long sessionId; do { - sessionId = Math.abs(mRandom.nextLong()); - if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != 0) { + final long randomLong = mRandom.nextLong(); + sessionId = (randomLong == Long.MIN_VALUE) ? INVALID_BLOB_ID : Math.abs(randomLong); + if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != INVALID_BLOB_ID) { return sessionId; } } while (n++ < 32); @@ -321,9 +336,26 @@ public class BlobStoreManagerService extends SystemService { mKnownBlobIds.add(id); } + @GuardedBy("mBlobsLock") + private int getSessionsCountLocked(int uid, String packageName) { + // TODO: Maintain a counter instead of traversing all the sessions + final AtomicInteger sessionsCount = new AtomicInteger(0); + forEachSessionInUser(session -> { + if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) { + sessionsCount.getAndIncrement(); + } + }, UserHandle.getUserId(uid)); + return sessionsCount.get(); + } + private long createSessionInternal(BlobHandle blobHandle, int callingUid, String callingPackage) { synchronized (mBlobsLock) { + final int sessionsCount = getSessionsCountLocked(callingUid, callingPackage); + if (sessionsCount >= getMaxActiveSessions()) { + throw new LimitExceededException("Too many active sessions for the caller: " + + sessionsCount); + } // TODO: throw if there is already an active session associated with blobHandle. final long sessionId = generateNextSessionIdLocked(); final BlobStoreSession session = new BlobStoreSession(mContext, @@ -376,34 +408,102 @@ public class BlobStoreManagerService extends SystemService { .get(blobHandle); if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( callingPackage, callingUid)) { + if (blobMetadata == null) { + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, + INVALID_BLOB_ID, INVALID_BLOB_SIZE, + FrameworkStatsLog.BLOB_OPENED__RESULT__BLOB_DNE); + } else { + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED); + } throw new SecurityException("Caller not allowed to access " + blobHandle + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); } + + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS); + return blobMetadata.openForRead(callingPackage); } } + @GuardedBy("mBlobsLock") + private int getCommittedBlobsCountLocked(int uid, String packageName) { + // TODO: Maintain a counter instead of traversing all the blobs + final AtomicInteger blobsCount = new AtomicInteger(0); + forEachBlobInUser((blobMetadata) -> { + if (blobMetadata.isACommitter(packageName, uid)) { + blobsCount.getAndIncrement(); + } + }, UserHandle.getUserId(uid)); + return blobsCount.get(); + } + + @GuardedBy("mBlobsLock") + private int getLeasedBlobsCountLocked(int uid, String packageName) { + // TODO: Maintain a counter instead of traversing all the blobs + final AtomicInteger blobsCount = new AtomicInteger(0); + forEachBlobInUser((blobMetadata) -> { + if (blobMetadata.isALeasee(packageName, uid)) { + blobsCount.getAndIncrement(); + } + }, UserHandle.getUserId(uid)); + return blobsCount.get(); + } + private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId, CharSequence description, long leaseExpiryTimeMillis, int callingUid, String callingPackage) { synchronized (mBlobsLock) { + final int leasesCount = getLeasedBlobsCountLocked(callingUid, callingPackage); + if (leasesCount >= getMaxLeasedBlobs()) { + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + INVALID_BLOB_ID, INVALID_BLOB_SIZE, + FrameworkStatsLog.BLOB_LEASED__RESULT__COUNT_LIMIT_EXCEEDED); + throw new LimitExceededException("Too many leased blobs for the caller: " + + leasesCount); + } final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid)) .get(blobHandle); if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( callingPackage, callingUid)) { + if (blobMetadata == null) { + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + INVALID_BLOB_ID, INVALID_BLOB_SIZE, + FrameworkStatsLog.BLOB_LEASED__RESULT__BLOB_DNE); + } else { + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED); + } throw new SecurityException("Caller not allowed to access " + blobHandle + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); } if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0 && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) { + + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID); throw new IllegalArgumentException( "Lease expiry cannot be later than blobs expiry time"); } if (blobMetadata.getSize() > getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) { + + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_LEASED__RESULT__DATA_SIZE_LIMIT_EXCEEDED); throw new LimitExceededException("Total amount of data with an active lease" + " is exceeding the max limit"); } + + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + blobMetadata.getBlobId(), blobMetadata.getSize(), + FrameworkStatsLog.BLOB_LEASED__RESULT__SUCCESS); + blobMetadata.addOrReplaceLeasee(callingPackage, callingUid, descriptionResId, description, leaseExpiryTimeMillis); if (LOGV) { @@ -442,9 +542,21 @@ public class BlobStoreManagerService extends SystemService { Slog.v(TAG, "Released lease on " + blobHandle + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); } - if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { - deleteBlobLocked(blobMetadata); - userBlobs.remove(blobHandle); + if (!blobMetadata.hasValidLeases()) { + mHandler.postDelayed(() -> { + synchronized (mBlobsLock) { + // Check if blobMetadata object is still valid. If it is not, then + // it means that it was already deleted and nothing else to do here. + if (!Objects.equals(userBlobs.get(blobHandle), blobMetadata)) { + return; + } + if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { + deleteBlobLocked(blobMetadata); + userBlobs.remove(blobHandle); + } + writeBlobsInfoAsync(); + } + }, getDeletionOnLastLeaseDelayMs()); } writeBlobsInfoAsync(); } @@ -474,15 +586,21 @@ public class BlobStoreManagerService extends SystemService { getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> { final ArrayList leaseInfos = new ArrayList<>(); blobMetadata.forEachLeasee(leasee -> { + if (!leasee.isStillValid()) { + return; + } final int descriptionResId = leasee.descriptionResEntryName == null ? Resources.ID_NULL : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName), leasee.descriptionResEntryName, leasee.packageName); - leaseInfos.add(new LeaseInfo(leasee.packageName, leasee.expiryTimeMillis, + final long expiryTimeMs = leasee.expiryTimeMillis == 0 + ? blobHandle.getExpiryTimeMillis() : leasee.expiryTimeMillis; + leaseInfos.add(new LeaseInfo(leasee.packageName, expiryTimeMs, descriptionResId, leasee.description)); }); blobInfos.add(new BlobInfo(blobMetadata.getBlobId(), - blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), leaseInfos)); + blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), + blobMetadata.getSize(), leaseInfos)); }); } return blobInfos; @@ -494,7 +612,11 @@ public class BlobStoreManagerService extends SystemService { UserHandle.getUserId(callingUid)); userBlobs.entrySet().removeIf(entry -> { final BlobMetadata blobMetadata = entry.getValue(); - return blobMetadata.getBlobId() == blobId; + if (blobMetadata.getBlobId() == blobId) { + deleteBlobLocked(blobMetadata); + return true; + } + return false; }); writeBlobsInfoAsync(); } @@ -545,11 +667,10 @@ public class BlobStoreManagerService extends SystemService { switch (session.getState()) { case STATE_ABANDONED: case STATE_VERIFIED_INVALID: - session.getSessionFile().delete(); synchronized (mBlobsLock) { + deleteSessionLocked(session); getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) .remove(session.getSessionId()); - mActiveBlobIds.remove(session.getSessionId()); if (LOGV) { Slog.v(TAG, "Session is invalid; deleted " + session); } @@ -564,6 +685,20 @@ public class BlobStoreManagerService extends SystemService { break; case STATE_VERIFIED_VALID: synchronized (mBlobsLock) { + final int committedBlobsCount = getCommittedBlobsCountLocked( + session.getOwnerUid(), session.getOwnerPackageName()); + if (committedBlobsCount >= getMaxCommittedBlobs()) { + Slog.d(TAG, "Failed to commit: too many committed blobs. count: " + + committedBlobsCount + "; blob: " + session); + session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); + deleteSessionLocked(session); + getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) + .remove(session.getSessionId()); + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, + session.getOwnerUid(), session.getSessionId(), session.getSize(), + FrameworkStatsLog.BLOB_COMMITTED__RESULT__COUNT_LIMIT_EXCEEDED); + break; + } final int userId = UserHandle.getUserId(session.getOwnerUid()); final ArrayMap userBlobs = getUserBlobsLocked( userId); @@ -584,6 +719,9 @@ public class BlobStoreManagerService extends SystemService { blob.addOrReplaceCommitter(newCommitter); try { writeBlobsInfoLocked(); + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, + session.getOwnerUid(), blob.getBlobId(), blob.getSize(), + FrameworkStatsLog.BLOB_COMMITTED__RESULT__SUCCESS); session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS); } catch (Exception e) { if (existingCommitter == null) { @@ -591,7 +729,21 @@ public class BlobStoreManagerService extends SystemService { } else { blob.addOrReplaceCommitter(existingCommitter); } + Slog.d(TAG, "Error committing the blob: " + session, e); + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, + session.getOwnerUid(), session.getSessionId(), blob.getSize(), + FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT); session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); + // If the commit fails and this blob data didn't exist before, delete it. + // But if it is a recommit, just leave it as is. + if (session.getSessionId() == blob.getBlobId()) { + deleteBlobLocked(blob); + userBlobs.remove(blob.getBlobHandle()); + } + } + // Delete redundant data from recommits. + if (session.getSessionId() != blob.getBlobId()) { + deleteSessionLocked(session); } getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) .remove(session.getSessionId()); @@ -779,8 +931,8 @@ public class BlobStoreManagerService extends SystemService { blobMetadata.getBlobFile().delete(); } else { addBlobForUserLocked(blobMetadata, blobMetadata.getUserId()); - blobMetadata.removeInvalidCommitters(userPackages); - blobMetadata.removeInvalidLeasees(userPackages); + blobMetadata.removeCommittersFromUnknownPkgs(userPackages); + blobMetadata.removeLeaseesFromUnknownPkgs(userPackages); } mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId()); } @@ -877,8 +1029,7 @@ public class BlobStoreManagerService extends SystemService { userSessions.removeIf((sessionId, blobStoreSession) -> { if (blobStoreSession.getOwnerUid() == uid && blobStoreSession.getOwnerPackageName().equals(packageName)) { - blobStoreSession.getSessionFile().delete(); - mActiveBlobIds.remove(blobStoreSession.getSessionId()); + deleteSessionLocked(blobStoreSession); return true; } return false; @@ -919,8 +1070,7 @@ public class BlobStoreManagerService extends SystemService { if (userSessions != null) { for (int i = 0, count = userSessions.size(); i < count; ++i) { final BlobStoreSession session = userSessions.valueAt(i); - session.getSessionFile().delete(); - mActiveBlobIds.remove(session.getSessionId()); + deleteSessionLocked(session); } } @@ -969,6 +1119,9 @@ public class BlobStoreManagerService extends SystemService { userBlobs.entrySet().removeIf(entry -> { final BlobMetadata blobMetadata = entry.getValue(); + // Remove expired leases + blobMetadata.removeExpiredLeases(); + if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { deleteBlobLocked(blobMetadata); deletedBlobIds.add(blobMetadata.getBlobId()); @@ -996,28 +1149,41 @@ public class BlobStoreManagerService extends SystemService { } if (shouldRemove) { - blobStoreSession.getSessionFile().delete(); - mActiveBlobIds.remove(blobStoreSession.getSessionId()); + deleteSessionLocked(blobStoreSession); deletedBlobIds.add(blobStoreSession.getSessionId()); } return shouldRemove; }); } - if (LOGV) { - Slog.v(TAG, "Completed idle maintenance; deleted " - + Arrays.toString(deletedBlobIds.toArray())); - } + Slog.d(TAG, "Completed idle maintenance; deleted " + + Arrays.toString(deletedBlobIds.toArray())); writeBlobSessionsAsync(); } + @GuardedBy("mBlobsLock") + private void deleteSessionLocked(BlobStoreSession blobStoreSession) { + blobStoreSession.destroy(); + mActiveBlobIds.remove(blobStoreSession.getSessionId()); + } + @GuardedBy("mBlobsLock") private void deleteBlobLocked(BlobMetadata blobMetadata) { - blobMetadata.getBlobFile().delete(); + blobMetadata.destroy(); mActiveBlobIds.remove(blobMetadata.getBlobId()); } void runClearAllSessions(@UserIdInt int userId) { synchronized (mBlobsLock) { + for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { + final int sessionUserId = mSessions.keyAt(i); + if (userId != UserHandle.USER_ALL && userId != sessionUserId) { + continue; + } + final LongSparseArray userSessions = mSessions.valueAt(i); + for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { + mActiveBlobIds.remove(userSessions.valueAt(j).getSessionId()); + } + } if (userId == UserHandle.USER_ALL) { mSessions.clear(); } else { @@ -1029,6 +1195,16 @@ public class BlobStoreManagerService extends SystemService { void runClearAllBlobs(@UserIdInt int userId) { synchronized (mBlobsLock) { + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final int blobUserId = mBlobsMap.keyAt(i); + if (userId != UserHandle.USER_ALL && userId != blobUserId) { + continue; + } + final ArrayMap userBlobs = mBlobsMap.valueAt(i); + for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { + mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId()); + } + } if (userId == UserHandle.USER_ALL) { mBlobsMap.clear(); } else { @@ -1253,8 +1429,11 @@ public class BlobStoreManagerService extends SystemService { + "callingUid=" + callingUid + ", callingPackage=" + packageName); } - // TODO: Verify caller request is within limits (no. of calls/blob sessions/blobs) - return createSessionInternal(blobHandle, callingUid, packageName); + try { + return createSessionInternal(blobHandle, callingUid, packageName); + } catch (LimitExceededException e) { + throw new ParcelableException(e); + } } @Override @@ -1321,6 +1500,8 @@ public class BlobStoreManagerService extends SystemService { "leaseExpiryTimeMillis must not be negative"); Objects.requireNonNull(packageName, "packageName must not be null"); + description = BlobStoreConfig.getTruncatedLeaseDescription(description); + final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -1488,7 +1669,7 @@ public class BlobStoreManagerService extends SystemService { public int handleShellCommand(@NonNull ParcelFileDescriptor in, @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args) { - return (new BlobStoreManagerShellCommand(BlobStoreManagerService.this)).exec(this, + return new BlobStoreManagerShellCommand(BlobStoreManagerService.this).exec(this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); } } @@ -1680,6 +1861,40 @@ public class BlobStoreManagerService extends SystemService { } } + private void registerBlobStorePuller() { + mStatsManager.setPullAtomCallback( + FrameworkStatsLog.BLOB_INFO, + null, // use default PullAtomMetadata values + BackgroundThread.getExecutor(), + mStatsCallbackImpl + ); + } + + private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback { + @Override + public int onPullAtom(int atomTag, List data) { + switch (atomTag) { + case FrameworkStatsLog.BLOB_INFO: + return pullBlobData(atomTag, data); + default: + throw new UnsupportedOperationException("Unknown tagId=" + atomTag); + } + } + } + + private int pullBlobData(int atomTag, List data) { + synchronized (mBlobsLock) { + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final ArrayMap userBlobs = mBlobsMap.valueAt(i); + for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { + final BlobMetadata blob = userBlobs.valueAt(j); + data.add(blob.dumpAsStatsEvent(atomTag)); + } + } + } + return StatsManager.PULL_SUCCESS; + } + private class LocalService extends BlobStoreManagerInternal { @Override public void onIdleMaintenance() { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index 2b0458303a23e3265631e9e5b15be700a500f289..2f83be1e0370ad60b62e0c97aa565a517472b57b 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -27,10 +27,12 @@ import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_RDWR; import static android.system.OsConstants.SEEK_SET; +import static android.text.format.Formatter.FLAG_IEC_UNITS; +import static android.text.format.Formatter.formatFileSize; -import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME; +import static com.android.server.blob.BlobStoreConfig.getMaxPermittedPackages; import static com.android.server.blob.BlobStoreConfig.hasSessionExpired; import android.annotation.BytesLong; @@ -42,7 +44,9 @@ import android.app.blob.IBlobStoreSession; import android.content.Context; import android.os.Binder; import android.os.FileUtils; +import android.os.LimitExceededException; import android.os.ParcelFileDescriptor; +import android.os.ParcelableException; import android.os.RemoteException; import android.os.RevocableFileDescriptor; import android.os.Trace; @@ -54,12 +58,15 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.blob.BlobStoreManagerService.DumpArgs; import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -72,7 +79,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; -/** TODO: add doc */ +/** + * Class to represent the state corresponding to an ongoing + * {@link android.app.blob.BlobStoreManager.Session} + */ @VisibleForTesting class BlobStoreSession extends IBlobStoreSession.Stub { @@ -98,7 +108,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { private File mSessionFile; @GuardedBy("mRevocableFds") - private ArrayList mRevocableFds = new ArrayList<>(); + private final ArrayList mRevocableFds = new ArrayList<>(); // This will be accessed from only one thread at any point of time, so no need to grab // a lock for this. @@ -208,27 +218,37 @@ class BlobStoreSession extends IBlobStoreSession.Stub { throw new IllegalStateException("Not allowed to write in state: " + stateToString(mState)); } + } - try { - return openWriteLocked(offsetBytes, lengthBytes); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); + FileDescriptor fd = null; + try { + fd = openWriteInternal(offsetBytes, lengthBytes); + final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); + synchronized (mSessionLock) { + if (mState != STATE_OPENED) { + IoUtils.closeQuietly(fd); + throw new IllegalStateException("Not allowed to write in state: " + + stateToString(mState)); + } + trackRevocableFdLocked(revocableFd); + return revocableFd.getRevocableFileDescriptor(); } + } catch (IOException e) { + IoUtils.closeQuietly(fd); + throw ExceptionUtils.wrap(e); } } - @GuardedBy("mSessionLock") @NonNull - private ParcelFileDescriptor openWriteLocked(@BytesLong long offsetBytes, + private FileDescriptor openWriteInternal(@BytesLong long offsetBytes, @BytesLong long lengthBytes) throws IOException { // TODO: Add limit on active open sessions/writes/reads - FileDescriptor fd = null; try { final File sessionFile = getSessionFile(); if (sessionFile == null) { throw new IllegalStateException("Couldn't get the file for this session"); } - fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600); + final FileDescriptor fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600); if (offsetBytes > 0) { final long curOffset = Os.lseek(fd, offsetBytes, SEEK_SET); if (curOffset != offsetBytes) { @@ -239,10 +259,10 @@ class BlobStoreSession extends IBlobStoreSession.Stub { if (lengthBytes > 0) { mContext.getSystemService(StorageManager.class).allocateBytes(fd, lengthBytes); } + return fd; } catch (ErrnoException e) { - e.rethrowAsIOException(); + throw e.rethrowAsIOException(); } - return createRevocableFdLocked(fd); } @Override @@ -254,29 +274,46 @@ class BlobStoreSession extends IBlobStoreSession.Stub { throw new IllegalStateException("Not allowed to read in state: " + stateToString(mState)); } + if (!BlobStoreConfig.shouldUseRevocableFdForReads()) { + try { + return new ParcelFileDescriptor(openReadInternal()); + } catch (IOException e) { + throw ExceptionUtils.wrap(e); + } + } + } - try { - return openReadLocked(); - } catch (IOException e) { - throw ExceptionUtils.wrap(e); + FileDescriptor fd = null; + try { + fd = openReadInternal(); + final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); + synchronized (mSessionLock) { + if (mState != STATE_OPENED) { + IoUtils.closeQuietly(fd); + throw new IllegalStateException("Not allowed to read in state: " + + stateToString(mState)); + } + trackRevocableFdLocked(revocableFd); + return revocableFd.getRevocableFileDescriptor(); } + } catch (IOException e) { + IoUtils.closeQuietly(fd); + throw ExceptionUtils.wrap(e); } } - @GuardedBy("mSessionLock") @NonNull - private ParcelFileDescriptor openReadLocked() throws IOException { - FileDescriptor fd = null; + private FileDescriptor openReadInternal() throws IOException { try { final File sessionFile = getSessionFile(); if (sessionFile == null) { throw new IllegalStateException("Couldn't get the file for this session"); } - fd = Os.open(sessionFile.getPath(), O_RDONLY, 0); + final FileDescriptor fd = Os.open(sessionFile.getPath(), O_RDONLY, 0); + return fd; } catch (ErrnoException e) { - e.rethrowAsIOException(); + throw e.rethrowAsIOException(); } - return createRevocableFdLocked(fd); } @Override @@ -295,6 +332,11 @@ class BlobStoreSession extends IBlobStoreSession.Stub { throw new IllegalStateException("Not allowed to change access type in state: " + stateToString(mState)); } + if (mBlobAccessMode.getNumWhitelistedPackages() >= getMaxPermittedPackages()) { + throw new ParcelableException(new LimitExceededException( + "Too many packages permitted to access the blob: " + + mBlobAccessMode.getNumWhitelistedPackages())); + } mBlobAccessMode.allowPackageAccess(packageName, certificate); } } @@ -397,7 +439,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } mState = state; - revokeAllFdsLocked(); + revokeAllFds(); if (sendCallback) { mListener.onStateChanged(this); @@ -423,30 +465,36 @@ class BlobStoreSession extends IBlobStoreSession.Stub { mState = STATE_VERIFIED_VALID; // Commit callback will be sent once the data is persisted. } else { - if (LOGV) { - Slog.v(TAG, "Digest of the data didn't match the given BlobHandle.digest"); - } + Slog.d(TAG, "Digest of the data (" + + (mDataDigest == null ? "null" : BlobHandle.safeDigest(mDataDigest)) + + ") didn't match the given BlobHandle.digest (" + + BlobHandle.safeDigest(mBlobHandle.digest) + ")"); mState = STATE_VERIFIED_INVALID; + + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, getOwnerUid(), mSessionId, + getSize(), FrameworkStatsLog.BLOB_COMMITTED__RESULT__DIGEST_MISMATCH); sendCommitCallbackResult(COMMIT_RESULT_ERROR); } mListener.onStateChanged(this); } } - @GuardedBy("mSessionLock") - private void revokeAllFdsLocked() { - for (int i = mRevocableFds.size() - 1; i >= 0; --i) { - mRevocableFds.get(i).revoke(); + void destroy() { + revokeAllFds(); + getSessionFile().delete(); + } + + private void revokeAllFds() { + synchronized (mRevocableFds) { + for (int i = mRevocableFds.size() - 1; i >= 0; --i) { + mRevocableFds.get(i).revoke(); + } + mRevocableFds.clear(); } - mRevocableFds.clear(); } @GuardedBy("mSessionLock") - @NonNull - private ParcelFileDescriptor createRevocableFdLocked(FileDescriptor fd) - throws IOException { - final RevocableFileDescriptor revocableFd = - new RevocableFileDescriptor(mContext, fd); + private void trackRevocableFdLocked(RevocableFileDescriptor revocableFd) { synchronized (mRevocableFds) { mRevocableFds.add(revocableFd); } @@ -455,7 +503,6 @@ class BlobStoreSession extends IBlobStoreSession.Stub { mRevocableFds.remove(revocableFd); } }); - return revocableFd.getRevocableFileDescriptor(); } @Nullable @@ -510,6 +557,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { fout.println("ownerUid: " + mOwnerUid); fout.println("ownerPkg: " + mOwnerPackageName); fout.println("creation time: " + BlobStoreUtils.formatTime(mCreationTimeMs)); + fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); fout.println("blobHandle:"); fout.increaseIndent(); diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index 231263579088f19aacd7e16b0b031c4a1537afda..42725c51fd874df867ed076ed75605e118175139 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -59,9 +59,9 @@ import java.util.List; * *

Note: Beginning with API 30 * ({@link android.os.Build.VERSION_CODES#R}), JobScheduler will throttle runaway applications. - * Calling {@link #schedule(JobInfo)} and other such methods with very high frequency is indicative - * of an app bug and so, to make sure the system doesn't get overwhelmed, JobScheduler will begin - * to throttle apps that show buggy behavior, regardless of target SDK version. + * Calling {@link #schedule(JobInfo)} and other such methods with very high frequency can have a + * high cost and so, to make sure the system doesn't get overwhelmed, JobScheduler will begin + * to throttle apps, regardless of target SDK version. */ @SystemService(Context.JOB_SCHEDULER_SERVICE) public abstract class JobScheduler { @@ -74,9 +74,16 @@ public abstract class JobScheduler { public @interface Result {} /** - * Returned from {@link #schedule(JobInfo)} when an invalid parameter was supplied. This can occur - * if the run-time for your job is too short, or perhaps the system can't resolve the - * requisite {@link JobService} in your package. + * Returned from {@link #schedule(JobInfo)} if a job wasn't scheduled successfully. Scheduling + * can fail for a variety of reasons, including, but not limited to: + *

    + *
  • an invalid parameter was supplied (eg. the run-time for your job is too short, or the + * system can't resolve the requisite {@link JobService} in your package)
  • + *
  • the app has too many jobs scheduled
  • + *
  • the app has tried to schedule too many jobs in a short amount of time
  • + *
+ * Attempting to schedule the job again immediately after receiving this result will not + * guarantee a successful schedule. */ public static final int RESULT_FAILURE = 0; /** @@ -89,6 +96,11 @@ public abstract class JobScheduler { * ID with the new information in the {@link JobInfo}. If a job with the given ID is currently * running, it will be stopped. * + *

Note: Scheduling a job can have a high cost, even if it's + * rescheduling the same job and the job didn't execute, especially on platform versions before + * version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to + * this API if calls are made too frequently in a short amount of time. + * * @param job The job you wish scheduled. See * {@link android.app.job.JobInfo.Builder JobInfo.Builder} for more detail on the sorts of jobs * you can schedule. 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 887d82c6413f8e8beb21b6be711cd533d1b39f66..e15f0f37fc62ec4b4c54eaafadbbcb35ad5851a5 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -71,7 +71,7 @@ public interface AppStandbyInternal { */ void postOneTimeCheckIdleStates(); - void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId); + void reportEvent(UsageEvents.Event event, int userId); void setLastJobRunTime(String packageName, int userId, long elapsedRealtime); @@ -150,9 +150,7 @@ public interface AppStandbyInternal { void clearCarrierPrivilegedApps(); - void flushToDisk(int userId); - - void flushDurationsToDisk(); + void flushToDisk(); void initializeDefaultsForSystemApps(int userId); @@ -162,7 +160,7 @@ public interface AppStandbyInternal { void postReportExemptedSyncStart(String packageName, int userId); - void dumpUser(IndentingPrintWriter idpw, int userId, List pkgs); + void dumpUsers(IndentingPrintWriter idpw, int[] userIds, List pkgs); void dumpState(String[] args, PrintWriter pw); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index e88865161dfa46bf0096bd8409b42708a6f31cdf..871e40fc9dfe518a1d8a335844cd320df85cd555 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -255,6 +255,18 @@ public class JobSchedulerService extends com.android.server.SystemService private final CountQuotaTracker mQuotaTracker; private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()"; + private static final String QUOTA_TRACKER_SCHEDULE_LOGGED = + ".schedulePersisted out-of-quota logged"; + private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED = new Category( + ".schedulePersisted()"); + private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED = new Category( + ".schedulePersisted out-of-quota logged"); + private static final Categorizer QUOTA_CATEGORIZER = (userId, packageName, tag) -> { + if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) { + return QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED; + } + return QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED; + }; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list @@ -271,6 +283,7 @@ public class JobSchedulerService extends com.android.server.SystemService ActivityManagerInternal mActivityManagerInternal; IBatteryStats mBatteryStats; DeviceIdleInternal mLocalDeviceIdleController; + @VisibleForTesting AppStateTracker mAppStateTracker; final UsageStatsManagerInternal mUsageStats; private final AppStandbyInternal mAppStandbyInternal; @@ -343,10 +356,7 @@ public class JobSchedulerService extends com.android.server.SystemService final StateController sc = mControllers.get(controller); sc.onConstantsUpdatedLocked(); } - mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS); - mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, - mConstants.API_QUOTA_SCHEDULE_COUNT, - mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); + updateQuotaTracker(); } catch (IllegalArgumentException e) { // Failed to parse the settings string, log this and move on // with defaults. @@ -356,6 +366,14 @@ public class JobSchedulerService extends com.android.server.SystemService } } + @VisibleForTesting + void updateQuotaTracker() { + mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS); + mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED, + mConstants.API_QUOTA_SCHEDULE_COUNT, + mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); + } + static class MaxJobCounts { private final KeyValueListParser.IntValue mTotal; private final KeyValueListParser.IntValue mMaxBg; @@ -508,6 +526,8 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms"; private static final String KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION = "aq_schedule_throw_exception"; + private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = + "aq_schedule_return_failure"; private static final int DEFAULT_MIN_READY_NON_ACTIVE_JOBS_COUNT = 5; private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS; @@ -521,6 +541,7 @@ public class JobSchedulerService extends com.android.server.SystemService private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250; private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS; private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true; + private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false; /** * Minimum # of non-ACTIVE jobs for which the JMS will be happy running some work early. @@ -624,6 +645,11 @@ public class JobSchedulerService extends com.android.server.SystemService */ public boolean API_QUOTA_SCHEDULE_THROW_EXCEPTION = DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION; + /** + * Whether or not to return a failure result when an app hits its schedule quota limit. + */ + public boolean API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = + DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -679,6 +705,9 @@ public class JobSchedulerService extends com.android.server.SystemService API_QUOTA_SCHEDULE_THROW_EXCEPTION = mParser.getBoolean( KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION, DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION); + API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = mParser.getBoolean( + KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT, + DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT); } void dump(IndentingPrintWriter pw) { @@ -713,6 +742,8 @@ public class JobSchedulerService extends com.android.server.SystemService pw.printPair(KEY_API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS).println(); pw.printPair(KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION, API_QUOTA_SCHEDULE_THROW_EXCEPTION).println(); + pw.printPair(KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT, + API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT).println(); pw.decreaseIndent(); } @@ -741,6 +772,8 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(ConstantsProto.API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS); proto.write(ConstantsProto.API_QUOTA_SCHEDULE_THROW_EXCEPTION, API_QUOTA_SCHEDULE_THROW_EXCEPTION); + proto.write(ConstantsProto.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT, + API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT); } } @@ -974,12 +1007,17 @@ public class JobSchedulerService extends com.android.server.SystemService public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, int userId, String tag) { - if (job.isPersisted()) { - // Only limit schedule calls for persisted jobs. + final String servicePkg = job.getService().getPackageName(); + if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) { + // Only limit schedule calls for persisted jobs scheduled by the app itself. final String pkg = packageName == null ? job.getService().getPackageName() : packageName; if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) { - Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times"); + if (mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED)) { + // Don't log too frequently + Slog.wtf(TAG, userId + "-" + pkg + " has called schedule() too many times"); + mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_LOGGED); + } mAppStandbyInternal.restrictApp( pkg, userId, UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY); if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION) { @@ -1005,13 +1043,17 @@ public class JobSchedulerService extends com.android.server.SystemService // Only throw the exception for debuggable apps. throw new LimitExceededException( "schedule()/enqueue() called more than " - + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY) + + mQuotaTracker.getLimit( + QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED) + " times in the past " - + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY) - + "ms"); + + mQuotaTracker.getWindowSizeMs( + QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED) + + "ms. See the documentation for more information."); } } - return JobScheduler.RESULT_FAILURE; + if (mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT) { + return JobScheduler.RESULT_FAILURE; + } } mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG); } @@ -1372,10 +1414,12 @@ public class JobSchedulerService extends com.android.server.SystemService // Set up the app standby bucketing tracker mStandbyTracker = new StandbyTracker(); mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); - mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER); - mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, + mQuotaTracker = new CountQuotaTracker(context, QUOTA_CATEGORIZER); + mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED, mConstants.API_QUOTA_SCHEDULE_COUNT, mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); + // Log at most once per minute. + mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED, 1, 60_000); mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class); mAppStandbyInternal.addListener(mStandbyTracker); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index 372ec981df02a2af8f62a83395bb9f4b455a8dfa..70155ee8472077d577b5ecec072e6ba85080a5cd 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -675,6 +675,14 @@ public class AppIdleHistory { return Long.parseLong(value); } + + public void writeAppIdleTimes() { + final int size = mIdleHistory.size(); + for (int i = 0; i < size; i++) { + writeAppIdleTimes(mIdleHistory.keyAt(i)); + } + } + public void writeAppIdleTimes(int userId) { FileOutputStream fos = null; AtomicFile appIdleFile = new AtomicFile(getUserFile(userId)); @@ -743,8 +751,18 @@ public class AppIdleHistory { } } - public void dump(IndentingPrintWriter idpw, int userId, List pkgs) { - idpw.println("App Standby States:"); + public void dumpUsers(IndentingPrintWriter idpw, int[] userIds, List pkgs) { + final int numUsers = userIds.length; + for (int i = 0; i < numUsers; i++) { + idpw.println(); + dumpUser(idpw, userIds[i], pkgs); + } + } + + private void dumpUser(IndentingPrintWriter idpw, int userId, List pkgs) { + idpw.print("User "); + idpw.print(userId); + idpw.println(" App Standby States:"); idpw.increaseIndent(); ArrayMap userHistory = mIdleHistory.get(userId); final long elapsedRealtime = SystemClock.elapsedRealtime(); 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 280a6870a5e1b52d53e658064299bbb842d65d0d..f36084386f486c22bedd7ea428d23e59193becd4 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -82,18 +82,18 @@ import android.os.BatteryStats; import android.os.Build; import android.os.Environment; import android.os.Handler; +import android.os.IDeviceIdleController; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.PowerWhitelistManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.provider.Settings.Global; import android.telephony.TelephonyManager; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Slog; @@ -205,6 +205,10 @@ public class AppStandbyController implements AppStandbyInternal { */ private static final long WAIT_FOR_ADMIN_DATA_TIMEOUT_MS = 10_000; + private static final int HEADLESS_APP_CHECK_FLAGS = + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS; + // To name the lock for stack traces static class Lock {} @@ -233,11 +237,19 @@ public class AppStandbyController implements AppStandbyInternal { * Set of system apps that are headless (don't have any declared activities, enabled or * disabled). Presence in this map indicates that the app is a headless system app. */ - @GuardedBy("mAppIdleLock") - private final ArrayMap mHeadlessSystemApps = new ArrayMap<>(); + @GuardedBy("mHeadlessSystemApps") + private final ArraySet mHeadlessSystemApps = new ArraySet<>(); private final CountDownLatch mAdminDataAvailableLatch = new CountDownLatch(1); + // Cache the active network scorer queried from the network scorer service + private volatile String mCachedNetworkScorer = null; + // The last time the network scorer service was queried + private volatile long mCachedNetworkScorerAtMillis = 0L; + // How long before querying the network scorer again. During this time, subsequent queries will + // get the cached value + private static final long NETWORK_SCORER_CACHE_DURATION_MILLIS = 5000L; + // Messages for the handler static final int MSG_INFORM_LISTENERS = 3; static final int MSG_FORCE_IDLE_STATE = 4; @@ -387,6 +399,7 @@ public class AppStandbyController implements AppStandbyInternal { DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver(); IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING); deviceStates.addAction(BatteryManager.ACTION_DISCHARGING); + deviceStates.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); mContext.registerReceiver(deviceStateReceiver, deviceStates); synchronized (mAppIdleLock) { @@ -442,13 +455,14 @@ public class AppStandbyController implements AppStandbyInternal { mSystemServicesReady = true; + // Offload to handler thread to avoid boot time impact. + mHandler.post(mInjector::updatePowerWhitelistCache); + boolean userFileExists; synchronized (mAppIdleLock) { userFileExists = mAppIdleHistory.userFileExists(UserHandle.USER_SYSTEM); } - loadHeadlessSystemAppCache(); - if (mPendingInitializeDefaults || !userFileExists) { initializeDefaultsForSystemApps(UserHandle.USER_SYSTEM); } @@ -458,6 +472,10 @@ public class AppStandbyController implements AppStandbyInternal { } } else if (phase == PHASE_BOOT_COMPLETED) { setChargingState(mInjector.isCharging()); + + // Offload to handler thread after boot completed to avoid boot time impact. This means + // that headless system apps may be put in a lower bucket until boot has completed. + mHandler.post(this::loadHeadlessSystemAppCache); } } @@ -849,7 +867,7 @@ public class AppStandbyController implements AppStandbyInternal { } @Override - public void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) { + public void reportEvent(UsageEvents.Event event, int userId) { if (!mAppIdleEnabled) return; final int eventType = event.getEventType(); if ((eventType == UsageEvents.Event.ACTIVITY_RESUMED @@ -863,6 +881,7 @@ public class AppStandbyController implements AppStandbyInternal { final String pkg = event.getPackageName(); final List linkedProfiles = getCrossProfileTargets(pkg, userId); synchronized (mAppIdleLock) { + final long elapsedRealtime = mInjector.elapsedRealtime(); reportEventLocked(pkg, eventType, elapsedRealtime, userId); final int size = linkedProfiles.size(); @@ -1079,15 +1098,11 @@ public class AppStandbyController implements AppStandbyInternal { return STANDBY_BUCKET_EXEMPTED; } if (mSystemServicesReady) { - try { - // We allow all whitelisted apps, including those that don't want to be whitelisted - // for idle mode, because app idle (aka app standby) is really not as big an issue - // for controlling who participates vs. doze mode. - if (mInjector.isNonIdleWhitelisted(packageName)) { - return STANDBY_BUCKET_EXEMPTED; - } - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); + // We allow all whitelisted apps, including those that don't want to be whitelisted + // for idle mode, because app idle (aka app standby) is really not as big an issue + // for controlling who participates vs. doze mode. + if (mInjector.isNonIdleWhitelisted(packageName)) { + return STANDBY_BUCKET_EXEMPTED; } if (isActiveDeviceAdmin(packageName, userId)) { @@ -1121,7 +1136,9 @@ public class AppStandbyController implements AppStandbyInternal { } private boolean isHeadlessSystemApp(String packageName) { - return mHeadlessSystemApps.containsKey(packageName); + synchronized (mHeadlessSystemApps) { + return mHeadlessSystemApps.contains(packageName); + } } @Override @@ -1154,6 +1171,8 @@ public class AppStandbyController implements AppStandbyInternal { return new int[0]; } + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "getIdleUidsForUser"); + final long elapsedRealtime = mInjector.elapsedRealtime(); List apps; @@ -1189,6 +1208,7 @@ public class AppStandbyController implements AppStandbyInternal { uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0)); } } + if (DEBUG) { Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime)); } @@ -1210,6 +1230,8 @@ public class AppStandbyController implements AppStandbyInternal { } } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + return res; } @@ -1576,8 +1598,16 @@ public class AppStandbyController implements AppStandbyInternal { } private boolean isActiveNetworkScorer(String packageName) { - String activeScorer = mInjector.getActiveNetworkScorer(); - return packageName != null && packageName.equals(activeScorer); + // Validity of network scorer cache is limited to a few seconds. Fetch it again + // if longer since query. + // This is a temporary optimization until there's a callback mechanism for changes to network scorer. + final long now = SystemClock.elapsedRealtime(); + if (mCachedNetworkScorer == null + || mCachedNetworkScorerAtMillis < now - NETWORK_SCORER_CACHE_DURATION_MILLIS) { + mCachedNetworkScorer = mInjector.getActiveNetworkScorer(); + mCachedNetworkScorerAtMillis = now; + } + return packageName != null && packageName.equals(mCachedNetworkScorer); } private void informListeners(String packageName, int userId, int bucket, int reason, @@ -1602,18 +1632,11 @@ public class AppStandbyController implements AppStandbyInternal { } } - @Override - public void flushToDisk(int userId) { - synchronized (mAppIdleLock) { - mAppIdleHistory.writeAppIdleTimes(userId); - } - } @Override - public void flushDurationsToDisk() { - // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be - // considered not-idle, which is the safest outcome in such an event. + public void flushToDisk() { synchronized (mAppIdleLock) { + mAppIdleHistory.writeAppIdleTimes(); mAppIdleHistory.writeAppIdleDurations(); } } @@ -1692,24 +1715,29 @@ public class AppStandbyController implements AppStandbyInternal { return; } try { - PackageInfo pi = mPackageManager.getPackageInfoAsUser(packageName, - PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS, - userId); + PackageInfo pi = mPackageManager.getPackageInfoAsUser( + packageName, HEADLESS_APP_CHECK_FLAGS, userId); evaluateSystemAppException(pi); } catch (PackageManager.NameNotFoundException e) { - mHeadlessSystemApps.remove(packageName); + synchronized (mHeadlessSystemApps) { + mHeadlessSystemApps.remove(packageName); + } } } - private void evaluateSystemAppException(@Nullable PackageInfo pkgInfo) { - if (pkgInfo.applicationInfo != null && pkgInfo.applicationInfo.isSystemApp()) { - synchronized (mAppIdleLock) { - if (pkgInfo.activities == null || pkgInfo.activities.length == 0) { - // Headless system app. - mHeadlessSystemApps.put(pkgInfo.packageName, true); - } else { - mHeadlessSystemApps.remove(pkgInfo.packageName); - } + /** Returns true if the exception status changed. */ + private boolean evaluateSystemAppException(@Nullable PackageInfo pkgInfo) { + if (pkgInfo == null || pkgInfo.applicationInfo == null + || (!pkgInfo.applicationInfo.isSystemApp() + && !pkgInfo.applicationInfo.isUpdatedSystemApp())) { + return false; + } + synchronized (mHeadlessSystemApps) { + if (pkgInfo.activities == null || pkgInfo.activities.length == 0) { + // Headless system app. + return mHeadlessSystemApps.add(pkgInfo.packageName); + } else { + return mHeadlessSystemApps.remove(pkgInfo.packageName); } } } @@ -1746,15 +1774,19 @@ public class AppStandbyController implements AppStandbyInternal { } } - /** Call on a system update to temporarily reset system app buckets. */ + /** Call on system boot to get the initial set of headless system apps. */ private void loadHeadlessSystemAppCache() { Slog.d(TAG, "Loading headless system app cache. appIdleEnabled=" + mAppIdleEnabled); final List packages = mPackageManager.getInstalledPackagesAsUser( - PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS, - UserHandle.USER_SYSTEM); + HEADLESS_APP_CHECK_FLAGS, UserHandle.USER_SYSTEM); final int packageCount = packages.size(); for (int i = 0; i < packageCount; i++) { - evaluateSystemAppException(packages.get(i)); + PackageInfo pkgInfo = packages.get(i); + if (pkgInfo != null && evaluateSystemAppException(pkgInfo)) { + mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, + UserHandle.USER_SYSTEM, -1, pkgInfo.packageName) + .sendToTarget(); + } } } @@ -1781,9 +1813,9 @@ public class AppStandbyController implements AppStandbyInternal { } @Override - public void dumpUser(IndentingPrintWriter idpw, int userId, List pkgs) { + public void dumpUsers(IndentingPrintWriter idpw, int[] userIds, List pkgs) { synchronized (mAppIdleLock) { - mAppIdleHistory.dump(idpw, userId, pkgs); + mAppIdleHistory.dumpUsers(idpw, userIds, pkgs); } } @@ -1794,8 +1826,6 @@ public class AppStandbyController implements AppStandbyInternal { + "): " + mCarrierPrivilegedApps); } - final long now = System.currentTimeMillis(); - pw.println(); pw.println("Settings:"); @@ -1852,12 +1882,17 @@ public class AppStandbyController implements AppStandbyInternal { pw.println(); pw.println("mHeadlessSystemApps=["); - for (int i = mHeadlessSystemApps.size() - 1; i >= 0; --i) { - pw.print(mHeadlessSystemApps.keyAt(i)); - pw.println(","); + synchronized (mHeadlessSystemApps) { + for (int i = mHeadlessSystemApps.size() - 1; i >= 0; --i) { + pw.print(" "); + pw.print(mHeadlessSystemApps.valueAt(i)); + pw.println(","); + } } pw.println("]"); pw.println(); + + mInjector.dump(pw); } /** @@ -1874,7 +1909,7 @@ public class AppStandbyController implements AppStandbyInternal { private PackageManagerInternal mPackageManagerInternal; private DisplayManager mDisplayManager; private PowerManager mPowerManager; - private PowerWhitelistManager mPowerWhitelistManager; + private IDeviceIdleController mDeviceIdleController; private CrossProfileAppsInternal mCrossProfileAppsInternal; int mBootPhase; /** @@ -1882,6 +1917,11 @@ public class AppStandbyController implements AppStandbyInternal { * automatically placed in the RESTRICTED bucket. */ long mAutoRestrictedBucketDelayMs = ONE_DAY; + /** + * Cached set of apps that are power whitelisted, including those not whitelisted from idle. + */ + @GuardedBy("mPowerWhitelistedApps") + private final ArraySet mPowerWhitelistedApps = new ArraySet<>(); Injector(Context context, Looper looper) { mContext = context; @@ -1898,7 +1938,8 @@ public class AppStandbyController implements AppStandbyInternal { void onBootPhase(int phase) { if (phase == PHASE_SYSTEM_SERVICES_READY) { - mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class); + mDeviceIdleController = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mBatteryStats = IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); @@ -1949,8 +1990,34 @@ public class AppStandbyController implements AppStandbyInternal { return mBatteryManager.isCharging(); } - boolean isNonIdleWhitelisted(String packageName) throws RemoteException { - return mPowerWhitelistManager.isWhitelisted(packageName, false); + boolean isNonIdleWhitelisted(String packageName) { + if (mBootPhase < PHASE_SYSTEM_SERVICES_READY) { + return false; + } + synchronized (mPowerWhitelistedApps) { + return mPowerWhitelistedApps.contains(packageName); + } + } + + private void updatePowerWhitelistCache() { + if (mBootPhase < PHASE_SYSTEM_SERVICES_READY) { + return; + } + try { + // Don't call out to DeviceIdleController with the lock held. + final String[] whitelistedPkgs = + mDeviceIdleController.getFullPowerWhitelistExceptIdle(); + synchronized (mPowerWhitelistedApps) { + mPowerWhitelistedApps.clear(); + final int len = whitelistedPkgs.length; + for (int i = 0; i < len; ++i) { + mPowerWhitelistedApps.add(whitelistedPkgs[i]); + } + } + } catch (RemoteException e) { + // Should not happen. + Slog.wtf(TAG, "Failed to get power whitelist", e); + } } boolean isRestrictedBucketEnabled() { @@ -2037,6 +2104,19 @@ public class AppStandbyController implements AppStandbyInternal { } return mCrossProfileAppsInternal.getTargetUserProfiles(pkg, userId); } + + void dump(PrintWriter pw) { + pw.println("mPowerWhitelistedApps=["); + synchronized (mPowerWhitelistedApps) { + for (int i = mPowerWhitelistedApps.size() - 1; i >= 0; --i) { + pw.print(" "); + pw.print(mPowerWhitelistedApps.valueAt(i)); + pw.println(","); + } + } + pw.println("]"); + pw.println(); + } } class AppStandbyHandler extends Handler { @@ -2122,6 +2202,11 @@ public class AppStandbyController implements AppStandbyInternal { case BatteryManager.ACTION_DISCHARGING: setChargingState(false); break; + case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: + if (mSystemServicesReady) { + mHandler.post(mInjector::updatePowerWhitelistCache); + } + break; } } } diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index ac501a510e80ee16b4c9d8d952f776ea9a13d9b9..4417b681efc370863ae0a520784e7f6f028c2e05 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -96,11 +96,6 @@ java_sdk_library { ":updatable-media-srcs", ], - // TODO(b/155480189) - Remove naming_scheme once references have been resolved. - // Temporary java_sdk_library component naming scheme to use to ease the transition from separate - // modules to java_sdk_library. - naming_scheme: "framework-modules", - libs: [ "framework_media_annotation", ], diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index 070b13b9e5928f7a98ef6944b40b5f8df7608468..e4b5d19e67c97313192fea9febb2b935facb1660 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -31,6 +31,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; +import com.google.android.exoplayer2.extractor.ChunkIndex; import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; @@ -203,6 +204,15 @@ public final class MediaParser { /** Returned by {@link #getDurationMicros()} when the duration is unknown. */ public static final int UNKNOWN_DURATION = Integer.MIN_VALUE; + /** + * For each {@link #getSeekPoints} call, returns a single {@link SeekPoint} whose {@link + * SeekPoint#timeMicros} matches the requested timestamp, and whose {@link + * SeekPoint#position} is 0. + * + * @hide + */ + public static final SeekMap DUMMY = new SeekMap(new DummyExoPlayerSeekMap()); + private final com.google.android.exoplayer2.extractor.SeekMap mExoPlayerSeekMap; private SeekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) { @@ -219,7 +229,8 @@ public final class MediaParser { * duration is unknown. */ public long getDurationMicros() { - return mExoPlayerSeekMap.getDurationUs(); + long durationUs = mExoPlayerSeekMap.getDurationUs(); + return durationUs != C.TIME_UNSET ? durationUs : UNKNOWN_DURATION; } /** @@ -794,6 +805,79 @@ public final class MediaParser { */ public static final String PARAMETER_EAGERLY_EXPOSE_TRACKTYPE = "android.media.mediaparser.eagerlyExposeTrackType"; + /** + * Sets whether a dummy {@link SeekMap} should be exposed before starting extraction. {@code + * boolean} expected. Default value is {@code false}. + * + *

For each {@link SeekMap#getSeekPoints} call, the dummy {@link SeekMap} returns a single + * {@link SeekPoint} whose {@link SeekPoint#timeMicros} matches the requested timestamp, and + * whose {@link SeekPoint#position} is 0. + * + * @hide + */ + public static final String PARAMETER_EXPOSE_DUMMY_SEEKMAP = + "android.media.mediaparser.exposeDummySeekMap"; + + /** + * Sets whether chunk indices available in the extracted media should be exposed as {@link + * MediaFormat MediaFormats}. {@code boolean} expected. Default value is {@link false}. + * + *

When set to true, any information about media segmentation will be exposed as a {@link + * MediaFormat} (with track index 0) containing four {@link ByteBuffer} elements under the + * following keys: + * + *

    + *
  • "chunk-index-int-sizes": Contains {@code ints} representing the sizes in bytes of each + * of the media segments. + *
  • "chunk-index-long-offsets": Contains {@code longs} representing the byte offsets of + * each segment in the stream. + *
  • "chunk-index-long-us-durations": Contains {@code longs} representing the media duration + * of each segment, in microseconds. + *
  • "chunk-index-long-us-times": Contains {@code longs} representing the start time of each + * segment, in microseconds. + *
+ * + * @hide + */ + public static final String PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT = + "android.media.mediaParser.exposeChunkIndexAsMediaFormat"; + /** + * Sets a list of closed-caption {@link MediaFormat MediaFormats} that should be exposed as part + * of the extracted media. {@code List} expected. Default value is an empty list. + * + *

Expected keys in the {@link MediaFormat} are: + * + *

    + *

    {@link MediaFormat#KEY_MIME}: Determine the type of captions (for example, + * application/cea-608). Mandatory. + *

    {@link MediaFormat#KEY_CAPTION_SERVICE_NUMBER}: Determine the channel on which the + * captions are transmitted. Optional. + *

+ * + * @hide + */ + public static final String PARAMETER_EXPOSE_CAPTION_FORMATS = + "android.media.mediaParser.exposeCaptionFormats"; + /** + * Sets whether the value associated with {@link #PARAMETER_EXPOSE_CAPTION_FORMATS} should + * override any in-band caption service declarations. {@code boolean} expected. Default value is + * {@link false}. + * + *

When {@code false}, any present in-band caption services information will override the + * values associated with {@link #PARAMETER_EXPOSE_CAPTION_FORMATS}. + * + * @hide + */ + public static final String PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS = + "android.media.mediaParser.overrideInBandCaptionDeclarations"; + /** + * Sets whether a track for EMSG events should be exposed in case of parsing a container that + * supports them. {@code boolean} expected. Default value is {@link false}. + * + * @hide + */ + public static final String PARAMETER_EXPOSE_EMSG_TRACK = + "android.media.mediaParser.exposeEmsgTrack"; // Private constants. @@ -804,6 +888,7 @@ public final class MediaParser { private static final String TS_MODE_MULTI_PMT = "multi_pmt"; private static final String TS_MODE_HLS = "hls"; private static final int BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY = 6; + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; @IntDef( value = { @@ -953,10 +1038,13 @@ public final class MediaParser { private final DataReaderAdapter mScratchDataReaderAdapter; private final ParsableByteArrayAdapter mScratchParsableByteArrayAdapter; @Nullable private final Constructor mSchemeInitDataConstructor; + private final ArrayList mMuxedCaptionFormats; private boolean mInBandCryptoInfo; private boolean mIncludeSupplementalData; private boolean mIgnoreTimestampOffset; private boolean mEagerlyExposeTrackType; + private boolean mExposeDummySeekMap; + private boolean mExposeChunkIndexAsMediaFormat; private String mParserName; private Extractor mExtractor; private ExtractorInput mExtractorInput; @@ -1016,6 +1104,15 @@ public final class MediaParser { if (PARAMETER_EAGERLY_EXPOSE_TRACKTYPE.equals(parameterName)) { mEagerlyExposeTrackType = (boolean) value; } + if (PARAMETER_EXPOSE_DUMMY_SEEKMAP.equals(parameterName)) { + mExposeDummySeekMap = (boolean) value; + } + if (PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT.equals(parameterName)) { + mExposeChunkIndexAsMediaFormat = (boolean) value; + } + if (PARAMETER_EXPOSE_CAPTION_FORMATS.equals(parameterName)) { + setMuxedCaptionFormats((List) value); + } mParserParameters.put(parameterName, value); return this; } @@ -1054,8 +1151,8 @@ public final class MediaParser { * *

This method will block until some progress has been made. * - *

If this instance was created using {@link #create}. the first call to this method will - * sniff the content with the parsers with the provided names. + *

If this instance was created using {@link #create}, the first call to this method will + * sniff the content using the selected parser implementations. * * @param seekableInputReader The {@link SeekableInputReader} from which to obtain the media * container data. @@ -1077,11 +1174,10 @@ public final class MediaParser { } mExoDataReader.mInputReader = seekableInputReader; - // TODO: Apply parameters when creating extractor instances. if (mExtractor == null) { + mPendingExtractorInit = true; if (!mParserName.equals(PARSER_NAME_UNKNOWN)) { mExtractor = createExtractor(mParserName); - mExtractor.init(new ExtractorOutputAdapter()); } else { for (String parserName : mParserNamesPool) { Extractor extractor = createExtractor(parserName); @@ -1106,9 +1202,18 @@ public final class MediaParser { } if (mPendingExtractorInit) { + if (mExposeDummySeekMap) { + // We propagate the dummy seek map before initializing the extractor, in case the + // extractor initialization outputs a seek map. + mOutputConsumer.onSeekMapFound(SeekMap.DUMMY); + } mExtractor.init(new ExtractorOutputAdapter()); mPendingExtractorInit = false; + // We return after initialization to allow clients use any output information before + // starting actual extraction. + return true; } + if (isPendingSeek()) { mExtractor.seek(mPendingSeekPosition, mPendingSeekTimeMicros); removePendingSeek(); @@ -1122,6 +1227,7 @@ public final class MediaParser { throw new ParsingException(e); } if (result == Extractor.RESULT_END_OF_INPUT) { + mExtractorInput = null; return false; } if (result == Extractor.RESULT_SEEK) { @@ -1179,6 +1285,14 @@ public final class MediaParser { mScratchDataReaderAdapter = new DataReaderAdapter(); mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter(); mSchemeInitDataConstructor = getSchemeInitDataConstructor(); + mMuxedCaptionFormats = new ArrayList<>(); + } + + private void setMuxedCaptionFormats(List mediaFormats) { + mMuxedCaptionFormats.clear(); + for (MediaFormat mediaFormat : mediaFormats) { + mMuxedCaptionFormats.add(toExoPlayerCaptionFormat(mediaFormat)); + } } private boolean isPendingSeek() { @@ -1204,6 +1318,10 @@ public final class MediaParser { : 0; return new MatroskaExtractor(flags); case PARSER_NAME_FMP4: + flags |= + getBooleanParameter(PARAMETER_EXPOSE_EMSG_TRACK) + ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK + : 0; flags |= getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS) ? FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS @@ -1217,7 +1335,11 @@ public final class MediaParser { ? FragmentedMp4Extractor .FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME : 0; - return new FragmentedMp4Extractor(flags, timestampAdjuster); + return new FragmentedMp4Extractor( + flags, + timestampAdjuster, + /* sideloadedTrack= */ null, + mMuxedCaptionFormats); case PARSER_NAME_MP4: flags |= getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS) @@ -1268,6 +1390,10 @@ public final class MediaParser { getBooleanParameter(PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM) ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM : 0; + flags |= + getBooleanParameter(PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS) + ? DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS + : 0; String tsMode = getStringParameter(PARAMETER_TS_MODE, TS_MODE_SINGLE_PMT); int hlsMode = TS_MODE_SINGLE_PMT.equals(tsMode) @@ -1280,7 +1406,7 @@ public final class MediaParser { timestampAdjuster != null ? timestampAdjuster : new TimestampAdjuster(/* firstSampleTimestampUs= */ 0), - new DefaultTsPayloadReaderFactory(flags)); + new DefaultTsPayloadReaderFactory(flags, mMuxedCaptionFormats)); case PARSER_NAME_FLV: return new FlvExtractor(); case PARSER_NAME_OGG: @@ -1402,6 +1528,19 @@ public final class MediaParser { @Override public void seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) { + if (mExposeChunkIndexAsMediaFormat && exoplayerSeekMap instanceof ChunkIndex) { + ChunkIndex chunkIndex = (ChunkIndex) exoplayerSeekMap; + MediaFormat mediaFormat = new MediaFormat(); + mediaFormat.setByteBuffer("chunk-index-int-sizes", toByteBuffer(chunkIndex.sizes)); + mediaFormat.setByteBuffer( + "chunk-index-long-offsets", toByteBuffer(chunkIndex.offsets)); + mediaFormat.setByteBuffer( + "chunk-index-long-us-durations", toByteBuffer(chunkIndex.durationsUs)); + mediaFormat.setByteBuffer( + "chunk-index-long-us-times", toByteBuffer(chunkIndex.timesUs)); + mOutputConsumer.onTrackDataFound( + /* trackIndex= */ 0, new TrackData(mediaFormat, /* drmInitData= */ null)); + } mOutputConsumer.onSeekMapFound(new SeekMap(exoplayerSeekMap)); } } @@ -1549,6 +1688,9 @@ public final class MediaParser { if (cryptoData != mLastReceivedCryptoData) { mLastOutputCryptoInfo = createNewCryptoInfoAndPopulateWithCryptoData(cryptoData); + // We are using in-band crypto info, so the IV will be ignored. But we prevent + // it from being null because toString assumes it non-null. + mLastOutputCryptoInfo.iv = EMPTY_BYTE_ARRAY; } } else /* We must populate the full CryptoInfo. */ { // CryptoInfo.pattern is not accessible to the user, so the user needs to feed @@ -1682,6 +1824,28 @@ public final class MediaParser { } } + private static final class DummyExoPlayerSeekMap + implements com.google.android.exoplayer2.extractor.SeekMap { + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getDurationUs() { + return C.TIME_UNSET; + } + + @Override + public SeekPoints getSeekPoints(long timeUs) { + com.google.android.exoplayer2.extractor.SeekPoint seekPoint = + new com.google.android.exoplayer2.extractor.SeekPoint( + timeUs, /* position= */ 0); + return new SeekPoints(seekPoint, seekPoint); + } + } + /** Creates extractor instances. */ private interface ExtractorFactory { @@ -1691,6 +1855,16 @@ public final class MediaParser { // Private static methods. + private static Format toExoPlayerCaptionFormat(MediaFormat mediaFormat) { + Format.Builder formatBuilder = + new Format.Builder().setSampleMimeType(mediaFormat.getString(MediaFormat.KEY_MIME)); + if (mediaFormat.containsKey(MediaFormat.KEY_CAPTION_SERVICE_NUMBER)) { + formatBuilder.setAccessibilityChannel( + mediaFormat.getInteger(MediaFormat.KEY_CAPTION_SERVICE_NUMBER)); + } + return formatBuilder.build(); + } + private static MediaFormat toMediaFormat(Format format) { MediaFormat result = new MediaFormat(); setOptionalMediaFormatInt(result, MediaFormat.KEY_BIT_RATE, format.bitrate); @@ -1759,14 +1933,34 @@ public final class MediaParser { // format for convenient use from ExoPlayer. result.setString("crypto-mode-fourcc", format.drmInitData.schemeType); } + if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) { + result.setLong("subsample-offset-us-long", format.subsampleOffsetUs); + } // LACK OF SUPPORT FOR: - // format.containerMimeType; // format.id; // format.metadata; // format.stereoMode; return result; } + private static ByteBuffer toByteBuffer(long[] longArray) { + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(longArray.length * Long.BYTES); + for (long element : longArray) { + byteBuffer.putLong(element); + } + byteBuffer.flip(); + return byteBuffer; + } + + private static ByteBuffer toByteBuffer(int[] intArray) { + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(intArray.length * Integer.BYTES); + for (int element : intArray) { + byteBuffer.putInt(element); + } + byteBuffer.flip(); + return byteBuffer; + } + private static String toTypeString(int type) { switch (type) { case C.TRACK_TYPE_VIDEO: @@ -1922,6 +2116,15 @@ public final class MediaParser { expectedTypeByParameterName.put(PARAMETER_INCLUDE_SUPPLEMENTAL_DATA, Boolean.class); expectedTypeByParameterName.put(PARAMETER_IGNORE_TIMESTAMP_OFFSET, Boolean.class); expectedTypeByParameterName.put(PARAMETER_EAGERLY_EXPOSE_TRACKTYPE, Boolean.class); + expectedTypeByParameterName.put(PARAMETER_EXPOSE_DUMMY_SEEKMAP, Boolean.class); + expectedTypeByParameterName.put( + PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT, Boolean.class); + expectedTypeByParameterName.put( + PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS, Boolean.class); + expectedTypeByParameterName.put(PARAMETER_EXPOSE_EMSG_TRACK, Boolean.class); + // We do not check PARAMETER_EXPOSE_CAPTION_FORMATS here, and we do it in setParameters + // instead. Checking that the value is a List is insufficient to catch wrong parameter + // value types. EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName); } } diff --git a/apex/media/framework/java/android/media/MediaSession2.java b/apex/media/framework/java/android/media/MediaSession2.java index 081e76ab0215e9373f900ce5036bbbd743c31d56..6560afedab0fa584733ed25a147f21ad9681729c 100644 --- a/apex/media/framework/java/android/media/MediaSession2.java +++ b/apex/media/framework/java/android/media/MediaSession2.java @@ -404,7 +404,7 @@ public class MediaSession2 implements AutoCloseable { mCallback.onPostConnect(MediaSession2.this, controllerInfo); connected = true; } finally { - if (!connected) { + if (!connected || isClosed()) { if (DEBUG) { Log.d(TAG, "Rejecting connection or notifying that session is closed" + ", controllerInfo=" + controllerInfo); diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp index 732caecbd4a771f396be8a9d6e0e537b18d38b26..be553feb1d34bf6b924b7c503e95b7258211fcac 100644 --- a/apex/permission/framework/Android.bp +++ b/apex/permission/framework/Android.bp @@ -38,11 +38,6 @@ java_sdk_library { ":framework-permission-sources", ], - // TODO(b/155480189) - Remove naming_scheme once references have been resolved. - // Temporary java_sdk_library component naming scheme to use to ease the transition from separate - // modules to java_sdk_library. - naming_scheme: "framework-modules", - apex_available: [ "com.android.permission", "test_com.android.permission", diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp index 61449763540b7039471155d40152cb9a44f5184f..7f3187949712f160f04d5a4ef286581135ceaf8b 100644 --- a/apex/permission/service/Android.bp +++ b/apex/permission/service/Android.bp @@ -20,14 +20,26 @@ filegroup { path: "java", } -java_library { +java_sdk_library { name: "service-permission", + defaults: ["framework-system-server-module-defaults"], + visibility: [ + "//frameworks/base/services/core", + "//frameworks/base/apex/permission", + "//frameworks/base/apex/permission/testing", + "//frameworks/base/apex/permission/tests", + "//frameworks/base/services/tests/mockingservicestests", + ], + impl_library_visibility: [ + "//visibility:override", + "//frameworks/base/apex/permission/tests", + "//frameworks/base/services/tests/mockingservicestests", + "//frameworks/base/services/tests/servicestests", + ], srcs: [ ":service-permission-sources", ], - sdk_version: "module_current", libs: [ - "framework-annotations-lib", "framework-permission", ], apex_available: [ @@ -36,28 +48,3 @@ java_library { ], installable: true, } - -droidstubs { - name: "service-permission-stubs-srcs", - srcs: [ ":service-permission-sources" ], - defaults: ["service-module-stubs-srcs-defaults"], - check_api: { - last_released: { - api_file: ":service-permission.api.system-server.latest", - removed_api_file: ":service-permission-removed.api.system-server.latest", - }, - api_lint: { - new_since: ":service-permission.api.system-server.latest", - }, - }, - visibility: ["//visibility:private"], - dist: { dest: "service-permission.txt" }, -} - -java_library { - name: "service-permission-stubs", - srcs: [":service-permission-stubs-srcs"], - defaults: ["service-module-stubs-defaults"], - visibility: ["//frameworks/base/services/core"], - dist: { dest: "service-permission.jar" }, -} diff --git a/apex/permission/service/api/current.txt b/apex/permission/service/api/current.txt index c76cc3275737eb90679575c386f887226b2c65b7..d802177e249b3f97128699222e65c35e57ba7540 100644 --- a/apex/permission/service/api/current.txt +++ b/apex/permission/service/api/current.txt @@ -1,46 +1 @@ // Signature format: 2.0 -package com.android.permission.persistence { - - public interface RuntimePermissionsPersistence { - method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance(); - method public void deleteForUser(@NonNull android.os.UserHandle); - method @Nullable public com.android.permission.persistence.RuntimePermissionsState readForUser(@NonNull android.os.UserHandle); - method public void writeForUser(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle); - } - - public final class RuntimePermissionsState { - ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map>, @NonNull java.util.Map>); - method @Nullable public String getFingerprint(); - method @NonNull public java.util.Map> getPackagePermissions(); - method @NonNull public java.util.Map> getSharedUserPermissions(); - method public int getVersion(); - field public static final int NO_VERSION = -1; // 0xffffffff - } - - public static final class RuntimePermissionsState.PermissionState { - ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int); - method public int getFlags(); - method @NonNull public String getName(); - method public boolean isGranted(); - } - -} - -package com.android.role.persistence { - - public interface RolesPersistence { - method @NonNull public static com.android.role.persistence.RolesPersistence createInstance(); - method public void deleteForUser(@NonNull android.os.UserHandle); - method @Nullable public com.android.role.persistence.RolesState readForUser(@NonNull android.os.UserHandle); - method public void writeForUser(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle); - } - - public final class RolesState { - ctor public RolesState(int, @Nullable String, @NonNull java.util.Map>); - method @Nullable public String getPackagesHash(); - method @NonNull public java.util.Map> getRoles(); - method public int getVersion(); - } - -} - diff --git a/apex/permission/service/api/system-server-current.txt b/apex/permission/service/api/system-server-current.txt new file mode 100644 index 0000000000000000000000000000000000000000..c76cc3275737eb90679575c386f887226b2c65b7 --- /dev/null +++ b/apex/permission/service/api/system-server-current.txt @@ -0,0 +1,46 @@ +// Signature format: 2.0 +package com.android.permission.persistence { + + public interface RuntimePermissionsPersistence { + method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance(); + method public void deleteForUser(@NonNull android.os.UserHandle); + method @Nullable public com.android.permission.persistence.RuntimePermissionsState readForUser(@NonNull android.os.UserHandle); + method public void writeForUser(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle); + } + + public final class RuntimePermissionsState { + ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map>, @NonNull java.util.Map>); + method @Nullable public String getFingerprint(); + method @NonNull public java.util.Map> getPackagePermissions(); + method @NonNull public java.util.Map> getSharedUserPermissions(); + method public int getVersion(); + field public static final int NO_VERSION = -1; // 0xffffffff + } + + public static final class RuntimePermissionsState.PermissionState { + ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int); + method public int getFlags(); + method @NonNull public String getName(); + method public boolean isGranted(); + } + +} + +package com.android.role.persistence { + + public interface RolesPersistence { + method @NonNull public static com.android.role.persistence.RolesPersistence createInstance(); + method public void deleteForUser(@NonNull android.os.UserHandle); + method @Nullable public com.android.role.persistence.RolesState readForUser(@NonNull android.os.UserHandle); + method public void writeForUser(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle); + } + + public final class RolesState { + ctor public RolesState(int, @Nullable String, @NonNull java.util.Map>); + method @Nullable public String getPackagesHash(); + method @NonNull public java.util.Map> getRoles(); + method public int getVersion(); + } + +} + diff --git a/apex/permission/service/api/system-server-removed.txt b/apex/permission/service/api/system-server-removed.txt new file mode 100644 index 0000000000000000000000000000000000000000..d802177e249b3f97128699222e65c35e57ba7540 --- /dev/null +++ b/apex/permission/service/api/system-server-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/apex/permission/service/java/com/android/permission/persistence/IoUtils.java b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java index 0ae44603516e0e4022c815c0f5b5fe747ec20727..569a78c0ab410906a27f6467910e119ff15a1d4c 100644 --- a/apex/permission/service/java/com/android/permission/persistence/IoUtils.java +++ b/apex/permission/service/java/com/android/permission/persistence/IoUtils.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; /** * Utility class for IO. + * + * @hide */ public class IoUtils { diff --git a/apex/permission/tests/Android.bp b/apex/permission/tests/Android.bp index a1f7a544434c60733d6cb01728fa13ab0e94d9c7..271e328c11394cc31874f164ffbc80384f13dfec 100644 --- a/apex/permission/tests/Android.bp +++ b/apex/permission/tests/Android.bp @@ -19,7 +19,7 @@ android_test { "java/**/*.kt", ], static_libs: [ - "service-permission", + "service-permission.impl", "androidx.test.rules", "androidx.test.ext.junit", "androidx.test.ext.truth", diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index 78496c4074f840e2fd3769e9017fa77a547d04cd..d19faa97e223e6019a178228a5aa51404dd8494d 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -20,8 +20,8 @@ genrule { name: "statslog-statsd-java-gen", tools: ["stats-log-api-gen"], cmd: "$(location stats-log-api-gen) --java $(out) --module statsd" + - " --javaPackage com.android.internal.util --javaClass StatsdStatsLog", - out: ["com/android/internal/util/StatsdStatsLog.java"], + " --javaPackage com.android.internal.statsd --javaClass StatsdStatsLog", + out: ["com/android/internal/statsd/StatsdStatsLog.java"], } java_library_static { @@ -51,11 +51,6 @@ java_sdk_library { defaults: ["framework-module-defaults"], installable: true, - // TODO(b/155480189) - Remove naming_scheme once references have been resolved. - // Temporary java_sdk_library component naming scheme to use to ease the transition from separate - // modules to java_sdk_library. - naming_scheme: "framework-modules", - srcs: [ ":framework-statsd-sources", ], @@ -65,7 +60,7 @@ java_sdk_library { "android.os", "android.util", // From :statslog-statsd-java-gen - "com.android.internal.util", + "com.android.internal.statsd", ], api_packages: [ @@ -79,6 +74,7 @@ java_sdk_library { visibility: [ "//frameworks/base", // Framework "//frameworks/base/apex/statsd:__subpackages__", // statsd apex + "//frameworks/base/packages/Tethering", // Tethering "//frameworks/opt/net/wifi/service", // wifi service "//packages/providers/MediaProvider", // MediaProvider apk ], @@ -94,26 +90,3 @@ java_sdk_library { "test_com.android.os.statsd", ], } - -android_test { - name: "FrameworkStatsdTest", - platform_apis: true, - srcs: [ - // TODO(b/147705194): Use framework-statsd as a lib dependency instead. - ":framework-statsd-sources", - "test/**/*.java", - ], - manifest: "test/AndroidManifest.xml", - static_libs: [ - "androidx.test.rules", - "truth-prebuilt", - ], - libs: [ - "android.test.runner.stubs", - "android.test.base.stubs", - ], - test_suites: [ - "device-tests", - ], -} - diff --git a/apex/statsd/framework/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java index 8bd36a516b1272c74e62c07a39bf217b2544ad94..8be5c63f31e343504758173da181baf61486cac5 100644 --- a/apex/statsd/framework/java/android/util/StatsEvent.java +++ b/apex/statsd/framework/java/android/util/StatsEvent.java @@ -26,6 +26,8 @@ import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.util.Arrays; + /** * StatsEvent builds and stores the buffer sent over the statsd socket. * This class defines and encapsulates the socket protocol. @@ -224,7 +226,9 @@ public final class StatsEvent { // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag. // See android_util_StatsLog.cpp. - private static final int MAX_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4; + private static final int MAX_PUSH_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4; + + private static final int MAX_PULL_PAYLOAD_SIZE = 50 * 1024; // 50 KB private final int mAtomId; private final byte[] mPayload; @@ -619,6 +623,7 @@ public final class StatsEvent { @NonNull public Builder usePooledBuffer() { mUsePooledBuffer = true; + mBuffer.setMaxSize(MAX_PUSH_PAYLOAD_SIZE, mPos); return this; } @@ -694,8 +699,9 @@ public final class StatsEvent { @GuardedBy("sLock") private static Buffer sPool; - private final byte[] mBytes = new byte[MAX_PAYLOAD_SIZE]; + private byte[] mBytes = new byte[MAX_PUSH_PAYLOAD_SIZE]; private boolean mOverflow = false; + private int mMaxSize = MAX_PULL_PAYLOAD_SIZE; @NonNull private static Buffer obtain() { @@ -717,15 +723,26 @@ public final class StatsEvent { } private void release() { - synchronized (sLock) { - if (null == sPool) { - sPool = this; + // Recycle this Buffer if its size is MAX_PUSH_PAYLOAD_SIZE or under. + if (mBytes.length <= MAX_PUSH_PAYLOAD_SIZE) { + synchronized (sLock) { + if (null == sPool) { + sPool = this; + } } } } private void reset() { mOverflow = false; + mMaxSize = MAX_PULL_PAYLOAD_SIZE; + } + + private void setMaxSize(final int maxSize, final int numBytesWritten) { + mMaxSize = maxSize; + if (numBytesWritten > maxSize) { + mOverflow = true; + } } private boolean hasOverflowed() { @@ -740,11 +757,28 @@ public final class StatsEvent { * @return true if space is available, false otherwise. **/ private boolean hasEnoughSpace(final int index, final int numBytes) { - final boolean result = index + numBytes < MAX_PAYLOAD_SIZE; - if (!result) { + final int totalBytesNeeded = index + numBytes; + + if (totalBytesNeeded > mMaxSize) { mOverflow = true; + return false; } - return result; + + // Expand buffer if needed. + if (mBytes.length < mMaxSize && totalBytesNeeded > mBytes.length) { + int newSize = mBytes.length; + do { + newSize *= 2; + } while (newSize <= totalBytesNeeded); + + if (newSize > mMaxSize) { + newSize = mMaxSize; + } + + mBytes = Arrays.copyOf(mBytes, newSize); + } + + return true; } /** diff --git a/apex/statsd/framework/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java index 4eeae57fe19540e9147396eaa73f40abb91bf6b2..0a9f4ebabdf078191eabf7f0ac828f1976b84ae7 100644 --- a/apex/statsd/framework/java/android/util/StatsLog.java +++ b/apex/statsd/framework/java/android/util/StatsLog.java @@ -28,7 +28,7 @@ import android.os.IStatsd; import android.os.Process; import android.util.proto.ProtoOutputStream; -import com.android.internal.util.StatsdStatsLog; +import com.android.internal.statsd.StatsdStatsLog; /** * StatsLog provides an API for developers to send events to statsd. The events can be used to diff --git a/apex/statsd/framework/test/Android.bp b/apex/statsd/framework/test/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..b113d595b57c1d4a2bd654acdb064b549f40613a --- /dev/null +++ b/apex/statsd/framework/test/Android.bp @@ -0,0 +1,36 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +android_test { + name: "FrameworkStatsdTest", + platform_apis: true, + srcs: [ + // TODO(b/147705194): Use framework-statsd as a lib dependency instead. + ":framework-statsd-sources", + "**/*.java", + ], + manifest: "AndroidManifest.xml", + static_libs: [ + "androidx.test.rules", + "truth-prebuilt", + ], + libs: [ + "android.test.runner.stubs", + "android.test.base.stubs", + ], + test_suites: [ + "device-tests", + "mts", + ], +} \ No newline at end of file diff --git a/apex/statsd/framework/test/AndroidTest.xml b/apex/statsd/framework/test/AndroidTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..fb519150ecd56fc8578981f2dac81aad36391394 --- /dev/null +++ b/apex/statsd/framework/test/AndroidTest.xml @@ -0,0 +1,34 @@ + + + + + + + \ No newline at end of file diff --git a/apex/statsd/framework/test/src/android/util/StatsEventTest.java b/apex/statsd/framework/test/src/android/util/StatsEventTest.java index 7b511553a26f24d40e37329f775dea679cbc2f0e..8d263699d9c8fed099c2960098b39bd73994cefa 100644 --- a/apex/statsd/framework/test/src/android/util/StatsEventTest.java +++ b/apex/statsd/framework/test/src/android/util/StatsEventTest.java @@ -33,6 +33,7 @@ import org.junit.runner.RunWith; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Random; /** * Internal tests for {@link StatsEvent}. @@ -644,6 +645,165 @@ public class StatsEventTest { statsEvent.release(); } + @Test + public void testLargePulledEvent() { + final int expectedAtomId = 10_020; + byte[] field1 = new byte[10 * 1024]; + new Random().nextBytes(field1); + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = + StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()) + .isEqualTo(3); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()) + .isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("Third element is not byte array") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_BYTE_ARRAY); + + final byte[] field1Actual = getByteArrayFromByteBuffer(buffer); + assertWithMessage("Incorrect field 1").that(field1Actual).isEqualTo(field1); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test + public void testPulledEventOverflow() { + final int expectedAtomId = 10_020; + byte[] field1 = new byte[50 * 1024]; + new Random().nextBytes(field1); + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = + StatsEvent.newBuilder().setAtomId(expectedAtomId).writeByteArray(field1).build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()) + .isEqualTo(3); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()) + .isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("Third element is not errors type") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_ERRORS); + + final int errorMask = buffer.getInt(); + + assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask") + .that(errorMask) + .isEqualTo(StatsEvent.ERROR_OVERFLOW); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test + public void testPushedEventOverflow() { + final int expectedAtomId = 10_020; + byte[] field1 = new byte[10 * 1024]; + new Random().nextBytes(field1); + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(expectedAtomId) + .writeByteArray(field1) + .usePooledBuffer() + .build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()) + .isEqualTo(3); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()) + .isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id").that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("Third element is not errors type") + .that(buffer.get()) + .isEqualTo(StatsEvent.TYPE_ERRORS); + + final int errorMask = buffer.getInt(); + + assertWithMessage("ERROR_OVERFLOW should be the only error in the error mask") + .that(errorMask) + .isEqualTo(StatsEvent.ERROR_OVERFLOW); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + private static byte[] getByteArrayFromByteBuffer(final ByteBuffer buffer) { final int numBytes = buffer.getInt(); byte[] bytes = new byte[numBytes]; diff --git a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java index 240222ea9411ca0d13cac3d592f02f84bb12e690..6108a324e15e3bf4351e5ea7a3b9130207197e64 100644 --- a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java +++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java @@ -33,7 +33,6 @@ import com.android.internal.os.StatsdConfigProto.PullAtomPackages; import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; import com.android.internal.os.StatsdConfigProto.StatsdConfig; import com.android.internal.os.StatsdConfigProto.TimeUnit; -import com.android.internal.os.statsd.StatsConfigUtils; import com.android.internal.os.statsd.protos.TestAtoms; import com.android.os.AtomsProto.Atom; diff --git a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/StatsConfigUtils.java b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/StatsConfigUtils.java similarity index 99% rename from apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/StatsConfigUtils.java rename to apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/StatsConfigUtils.java index d0d140092586512db421341b40cdbd8fd1a0b4e8..b5afb94886de20b20a9fdb89a6c3e8bea25bfca9 100644 --- a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/StatsConfigUtils.java +++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/StatsConfigUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.internal.os.statsd; +package com.android.internal.os.statsd.libstats; import static com.google.common.truth.Truth.assertThat; diff --git a/api/test-current.txt b/api/test-current.txt index 6f3c9ee2df37ec4544d3988c7f9ecdb8a2a4c254..3838bad57aa71afde90d774b7ef096559e999ca3 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -32,6 +32,7 @@ package android { } public static final class R.bool { + field public static final int config_assistantOnTopOfDream = 17891333; // 0x1110005 field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004 } @@ -193,6 +194,7 @@ package android.app { method public static int opToDefaultMode(@NonNull String); method public static String opToPermission(int); method public static int permissionToOpCode(String); + method @RequiresPermission("android.permission.MANAGE_APPOPS") public void rebootHistory(long); method @RequiresPermission("android.permission.MANAGE_APPOPS") public void reloadNonHistoricalState(); method @RequiresPermission("android.permission.MANAGE_APPOPS") public void resetHistoryParameters(); method @RequiresPermission("android.permission.MANAGE_APPOPS") public void setHistoryParameters(int, long, int); @@ -1298,6 +1300,130 @@ package android.hardware.display { } +package android.hardware.hdmi { + + public final class HdmiControlManager { + method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient(); + method @RequiresPermission("android.permission.HDMI_CEC") public void setStandbyMode(boolean); + field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE"; + field public static final int AVR_VOLUME_MUTED = 101; // 0x65 + field public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 162; // 0xa2 + field public static final int CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION = 160; // 0xa0 + field public static final int CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE = 161; // 0xa1 + field public static final int CLEAR_TIMER_STATUS_TIMER_CLEARED = 128; // 0x80 + field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_INFO_AVAILABLE = 2; // 0x2 + field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_MATCHING = 1; // 0x1 + field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_RECORDING = 0; // 0x0 + field public static final int CONTROL_STATE_CHANGED_REASON_SETTING = 1; // 0x1 + field public static final int CONTROL_STATE_CHANGED_REASON_STANDBY = 3; // 0x3 + field public static final int CONTROL_STATE_CHANGED_REASON_START = 0; // 0x0 + field public static final int CONTROL_STATE_CHANGED_REASON_WAKEUP = 2; // 0x2 + field public static final int DEVICE_EVENT_ADD_DEVICE = 1; // 0x1 + field public static final int DEVICE_EVENT_REMOVE_DEVICE = 2; // 0x2 + field public static final int DEVICE_EVENT_UPDATE_DEVICE = 3; // 0x3 + field public static final String EXTRA_MESSAGE_EXTRA_PARAM1 = "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1"; + field public static final String EXTRA_MESSAGE_ID = "android.hardware.hdmi.extra.MESSAGE_ID"; + field public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 18; // 0x12 + field public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 51; // 0x33 + field public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 49; // 0x31 + field public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 13; // 0xd + field public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 14; // 0xe + field public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 50; // 0x32 + field public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 10; // 0xa + field public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 9; // 0x9 + field public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 21; // 0x15 + field public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 19; // 0x13 + field public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 22; // 0x16 + field public static final int ONE_TOUCH_RECORD_NO_MEDIA = 16; // 0x10 + field public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 12; // 0xc + field public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 20; // 0x14 + field public static final int ONE_TOUCH_RECORD_OTHER_REASON = 31; // 0x1f + field public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 23; // 0x17 + field public static final int ONE_TOUCH_RECORD_PLAYING = 17; // 0x11 + field public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 48; // 0x30 + field public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 27; // 0x1b + field public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 3; // 0x3 + field public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 1; // 0x1 + field public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 2; // 0x2 + field public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 4; // 0x4 + field public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 26; // 0x1a + field public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 6; // 0x6 + field public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 5; // 0x5 + field public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 7; // 0x7 + field public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 11; // 0xb + field public static final int OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT = 1; // 0x1 + field public static final int OSD_MESSAGE_AVR_VOLUME_CHANGED = 2; // 0x2 + field public static final int POWER_STATUS_ON = 0; // 0x0 + field public static final int POWER_STATUS_STANDBY = 1; // 0x1 + field public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; // 0x2 + field public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; // 0x3 + field public static final int POWER_STATUS_UNKNOWN = -1; // 0xffffffff + field @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4; // 0x4 + field public static final int RESULT_COMMUNICATION_FAILED = 7; // 0x7 + field public static final int RESULT_EXCEPTION = 5; // 0x5 + field public static final int RESULT_INCORRECT_MODE = 6; // 0x6 + field public static final int RESULT_SOURCE_NOT_AVAILABLE = 2; // 0x2 + field public static final int RESULT_SUCCESS = 0; // 0x0 + field public static final int RESULT_TARGET_NOT_AVAILABLE = 3; // 0x3 + field public static final int RESULT_TIMEOUT = 1; // 0x1 + field public static final int TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED = 3; // 0x3 + field public static final int TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 1; // 0x1 + field public static final int TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 2; // 0x2 + field public static final int TIMER_RECORDING_RESULT_EXTRA_NO_ERROR = 0; // 0x0 + field public static final int TIMER_RECORDING_TYPE_ANALOGUE = 2; // 0x2 + field public static final int TIMER_RECORDING_TYPE_DIGITAL = 1; // 0x1 + field public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3; // 0x3 + field public static final int TIMER_STATUS_MEDIA_INFO_NOT_PRESENT = 2; // 0x2 + field public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_NOT_PROTECTED = 0; // 0x0 + field public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_PROTECTED = 1; // 0x1 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_CA_NOT_SUPPORTED = 6; // 0x6 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_CLOCK_FAILURE = 10; // 0xa + field public static final int TIMER_STATUS_NOT_PROGRAMMED_DATE_OUT_OF_RANGE = 2; // 0x2 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_DUPLICATED = 14; // 0xe + field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PHYSICAL_NUMBER = 5; // 0x5 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PLUG_NUMBER = 4; // 0x4 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_SEQUENCE = 3; // 0x3 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_CA_ENTITLEMENTS = 7; // 0x7 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_FREE_TIME = 1; // 0x1 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_PARENTAL_LOCK_ON = 9; // 0x9 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_UNSUPPORTED_RESOLUTION = 8; // 0x8 + field public static final int TIMER_STATUS_PROGRAMMED_INFO_ENOUGH_SPACE = 8; // 0x8 + field public static final int TIMER_STATUS_PROGRAMMED_INFO_MIGHT_NOT_ENOUGH_SPACE = 11; // 0xb + field public static final int TIMER_STATUS_PROGRAMMED_INFO_NOT_ENOUGH_SPACE = 9; // 0x9 + field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa + } + + public final class HdmiControlServiceWrapper { + ctor public HdmiControlServiceWrapper(); + method @NonNull public android.hardware.hdmi.HdmiControlManager createHdmiControlManager(); + method @BinderThread public void setDeviceTypes(@NonNull int[]); + method @BinderThread public void setPortInfo(@NonNull java.util.List); + field public static final int DEVICE_PURE_CEC_SWITCH = 6; // 0x6 + } + + public final class HdmiPortInfo implements android.os.Parcelable { + ctor public HdmiPortInfo(int, int, int, boolean, boolean, boolean); + method public int describeContents(); + method public int getAddress(); + method public int getId(); + method public int getType(); + method public boolean isArcSupported(); + method public boolean isCecSupported(); + method public boolean isMhlSupported(); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int PORT_INPUT = 0; // 0x0 + field public static final int PORT_OUTPUT = 1; // 0x1 + } + + public class HdmiSwitchClient { + method public int getDeviceType(); + method @NonNull public java.util.List getPortInfo(); + method public void sendKeyEvent(int, boolean); + method public void sendVendorCommand(int, byte[], boolean); + } + +} + package android.hardware.lights { public final class Light implements android.os.Parcelable { @@ -3198,6 +3324,7 @@ package android.provider { field public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component"; field public static final String NOTIFICATION_BADGING = "notification_badging"; field public static final String POWER_MENU_LOCKED_SHOW_CONTENT = "power_menu_locked_show_content"; + field public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds"; field public static final String USER_SETUP_COMPLETE = "user_setup_complete"; field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; @@ -4977,6 +5104,7 @@ package android.view { method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut(); method public int getType(); method public boolean hasAccess(int); + field public static final int FLAG_TRUSTED = 128; // 0x80 field public static final int TYPE_EXTERNAL = 2; // 0x2 field public static final int TYPE_INTERNAL = 1; // 0x1 field public static final int TYPE_OVERLAY = 4; // 0x4 @@ -5021,7 +5149,7 @@ package android.view { } public final class SurfaceControl implements android.os.Parcelable { - ctor public SurfaceControl(@NonNull android.view.SurfaceControl); + ctor public SurfaceControl(@NonNull android.view.SurfaceControl, @NonNull String); method public static long acquireFrameRateFlexibilityToken(); method public boolean isSameSurface(@NonNull android.view.SurfaceControl); method public static void releaseFrameRateFlexibilityToken(long); @@ -5378,6 +5506,19 @@ package android.widget { } +package android.widget.inline { + + public class InlineContentView extends android.view.ViewGroup { + method public void setChildSurfacePackageUpdater(@Nullable android.widget.inline.InlineContentView.SurfacePackageUpdater); + } + + public static interface InlineContentView.SurfacePackageUpdater { + method public void getSurfacePackage(@NonNull java.util.function.Consumer); + method public void onSurfacePackageReleased(); + } + +} + package android.window { public final class DisplayAreaInfo implements android.os.Parcelable { diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java index 2adbc1f6e1aed1912296f4b804cba9fa184d2de7..7c30c8b1e1dd1a2f9b5d4fa8e044b4c3ad746959 100644 --- a/cmds/am/src/com/android/commands/am/Instrument.java +++ b/cmds/am/src/com/android/commands/am/Instrument.java @@ -17,8 +17,8 @@ package com.android.commands.am; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS; +import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS; -import static android.app.ActivityManager.INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL; import android.app.IActivityManager; import android.app.IInstrumentationWatcher; @@ -512,7 +512,7 @@ public class Instrument { flags |= INSTR_FLAG_DISABLE_TEST_API_CHECKS; } if (disableIsolatedStorage) { - flags |= INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL; + flags |= INSTR_FLAG_DISABLE_ISOLATED_STORAGE; } if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId, abi)) { diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 3bcabe56f89e7f14ea107c3c36036a8bc7bb5c55..bb2de17b42f37bb09972608295e362a19832814b 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -349,6 +349,25 @@ EGLConfig BootAnimation::getEglConfig(const EGLDisplay& display) { return config; } +ui::Size BootAnimation::limitSurfaceSize(int width, int height) const { + ui::Size limited(width, height); + bool wasLimited = false; + const float aspectRatio = float(width) / float(height); + if (mMaxWidth != 0 && width > mMaxWidth) { + limited.height = mMaxWidth / aspectRatio; + limited.width = mMaxWidth; + wasLimited = true; + } + if (mMaxHeight != 0 && limited.height > mMaxHeight) { + limited.height = mMaxHeight; + limited.width = mMaxHeight * aspectRatio; + wasLimited = true; + } + SLOGV_IF(wasLimited, "Surface size has been limited to [%dx%d] from [%dx%d]", + limited.width, limited.height, width, height); + return limited; +} + status_t BootAnimation::readyToRun() { mAssets.addDefaultAssets(); @@ -362,8 +381,10 @@ status_t BootAnimation::readyToRun() { if (error != NO_ERROR) return error; - const ui::Size& resolution = displayConfig.resolution; - + mMaxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0); + mMaxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0); + ui::Size resolution = displayConfig.resolution; + resolution = limitSurfaceSize(resolution.width, resolution.height); // create the native surface sp control = session()->createSurface(String8("BootAnimation"), resolution.getWidth(), resolution.getHeight(), PIXEL_FORMAT_RGB_565); @@ -459,8 +480,9 @@ void BootAnimation::resizeSurface(int newWidth, int newHeight) { eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(mDisplay, mSurface); - mWidth = newWidth; - mHeight = newHeight; + const auto limitedSize = limitSurfaceSize(newWidth, newHeight); + mWidth = limitedSize.width; + mHeight = limitedSize.height; SurfaceComposerClient::Transaction t; t.setSize(mFlingerSurfaceControl, mWidth, mHeight); diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index 36cd91bdee0d969fb0e923a3cbb30540a81cb147..6ba7fd450fbb2a0328071cafad8ae924fd99068c 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -170,6 +170,7 @@ private: bool findBootAnimationFileInternal(const std::vector& files); bool preloadAnimation(); EGLConfig getEglConfig(const EGLDisplay&); + ui::Size limitSurfaceSize(int width, int height) const; void resizeSurface(int newWidth, int newHeight); void checkExit(); @@ -181,6 +182,8 @@ private: Texture mAndroid[2]; int mWidth; int mHeight; + int mMaxWidth = 0; + int mMaxHeight = 0; int mCurrentInset; int mTargetInset; bool mUseNpotTextures = false; diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp index ef5c4cec9166a06ae49a457f864d05f1daa2ae87..878cef94b6743ce8ba91ab92f590c7ed0d7fd89a 100644 --- a/cmds/idmap2/Android.bp +++ b/cmds/idmap2/Android.bp @@ -23,9 +23,16 @@ cc_defaults { "misc-*", "readability-*", ], + tidy_checks_as_errors: [ + "modernize-*", + "-modernize-avoid-c-arrays", + "-modernize-use-trailing-return-type", + "android-*", + "misc-*", + "readability-*", + ], tidy_flags: [ "-system-headers", - "-warnings-as-errors=*", ], } @@ -168,13 +175,13 @@ cc_binary { ], host_supported: true, srcs: [ + "idmap2/CommandUtils.cpp", "idmap2/Create.cpp", "idmap2/CreateMultiple.cpp", "idmap2/Dump.cpp", "idmap2/Lookup.cpp", "idmap2/Main.cpp", "idmap2/Scan.cpp", - "idmap2/Verify.cpp", ], target: { android: { diff --git a/cmds/idmap2/idmap2/Verify.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp similarity index 70% rename from cmds/idmap2/idmap2/Verify.cpp rename to cmds/idmap2/idmap2/CommandUtils.cpp index 9cb67b33e6cf0529b59e55dfa29f4640df157d27..8f5845bf2e53e312f616ce3ee40742d379a3d5dd 100644 --- a/cmds/idmap2/idmap2/Verify.cpp +++ b/cmds/idmap2/idmap2/CommandUtils.cpp @@ -19,30 +19,19 @@ #include #include -#include "idmap2/CommandLineOptions.h" #include "idmap2/Idmap.h" #include "idmap2/Result.h" #include "idmap2/SysTrace.h" -using android::idmap2::CommandLineOptions; using android::idmap2::Error; using android::idmap2::IdmapHeader; using android::idmap2::Result; using android::idmap2::Unit; -Result Verify(const std::vector& args) { - SYSTRACE << "Verify " << args; - std::string idmap_path; - - const CommandLineOptions opts = - CommandLineOptions("idmap2 verify") - .MandatoryOption("--idmap-path", "input: path to idmap file to verify", &idmap_path); - - const auto opts_ok = opts.Parse(args); - if (!opts_ok) { - return opts_ok.GetError(); - } - +Result Verify(const std::string& idmap_path, const std::string& target_path, + const std::string& overlay_path, PolicyBitmask fulfilled_policies, + bool enforce_overlayable) { + SYSTRACE << "Verify " << idmap_path; std::ifstream fin(idmap_path); const std::unique_ptr header = IdmapHeader::FromBinaryStream(fin); fin.close(); @@ -50,7 +39,8 @@ Result Verify(const std::vector& args) { return Error("failed to parse idmap header"); } - const auto header_ok = header->IsUpToDate(); + const auto header_ok = header->IsUpToDate(target_path.c_str(), overlay_path.c_str(), + fulfilled_policies, enforce_overlayable); if (!header_ok) { return Error(header_ok.GetError(), "idmap not up to date"); } diff --git a/cmds/idmap2/idmap2/CommandUtils.h b/cmds/idmap2/idmap2/CommandUtils.h new file mode 100644 index 0000000000000000000000000000000000000000..e717e046d15d56dd1e26389199c06db2a88db147 --- /dev/null +++ b/cmds/idmap2/idmap2/CommandUtils.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#ifndef IDMAP2_IDMAP2_COMMAND_UTILS_H_ +#define IDMAP2_IDMAP2_COMMAND_UTILS_H_ + +#include "idmap2/PolicyUtils.h" +#include "idmap2/Result.h" + +android::idmap2::Result Verify(const std::string& idmap_path, + const std::string& target_path, + const std::string& overlay_path, + PolicyBitmask fulfilled_policies, + bool enforce_overlayable); + +#endif // IDMAP2_IDMAP2_COMMAND_UTILS_H_ diff --git a/cmds/idmap2/idmap2/Commands.h b/cmds/idmap2/idmap2/Commands.h index e626738a28956b2734bd7b6e5bd680182b1be5b5..69eea8d262d27e1d0032ab7988eed643fa9583fd 100644 --- a/cmds/idmap2/idmap2/Commands.h +++ b/cmds/idmap2/idmap2/Commands.h @@ -27,6 +27,5 @@ android::idmap2::Result CreateMultiple(const std::vector< android::idmap2::Result Dump(const std::vector& args); android::idmap2::Result Lookup(const std::vector& args); android::idmap2::Result Scan(const std::vector& args); -android::idmap2::Result Verify(const std::vector& args); #endif // IDMAP2_IDMAP2_COMMANDS_H_ diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp index 4b70acc2969c4e28ae89ac1df3391af46f2c7cb8..abdfaf4dccabfdefd15dcad16514416f7d74b38c 100644 --- a/cmds/idmap2/idmap2/CreateMultiple.cpp +++ b/cmds/idmap2/idmap2/CreateMultiple.cpp @@ -26,6 +26,7 @@ #include "android-base/stringprintf.h" #include "idmap2/BinaryStreamVisitor.h" #include "idmap2/CommandLineOptions.h" +#include "idmap2/CommandUtils.h" #include "idmap2/FileUtils.h" #include "idmap2/Idmap.h" #include "idmap2/Policies.h" @@ -103,7 +104,8 @@ Result CreateMultiple(const std::vector& args) { continue; } - if (!Verify(std::vector({"--idmap-path", idmap_path}))) { + if (!Verify(idmap_path, target_apk_path, overlay_apk_path, fulfilled_policies, + !ignore_overlayable)) { const std::unique_ptr overlay_apk = ApkAssets::Load(overlay_apk_path); if (!overlay_apk) { LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str(); diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp index a07e793d9f473c0d0c73b770e03ffd8841eb3819..fb093f0f22a42f4c0a665bd9aa31702c88356048 100644 --- a/cmds/idmap2/idmap2/Main.cpp +++ b/cmds/idmap2/idmap2/Main.cpp @@ -53,9 +53,8 @@ void PrintUsage(const NameToFunctionMap& commands, std::ostream& out) { int main(int argc, char** argv) { SYSTRACE << "main"; const NameToFunctionMap commands = { - {"create", Create}, {"create-multiple", CreateMultiple}, - {"dump", Dump}, {"lookup", Lookup}, - {"scan", Scan}, {"verify", Verify}, + {"create", Create}, {"create-multiple", CreateMultiple}, {"dump", Dump}, {"lookup", Lookup}, + {"scan", Scan}, }; if (argc <= 1) { PrintUsage(commands, std::cerr); diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp index da0453216f0321d266019df20c14d9e0e8f381b3..36250450cc74ec1052e38c6e45a292efa507257a 100644 --- a/cmds/idmap2/idmap2/Scan.cpp +++ b/cmds/idmap2/idmap2/Scan.cpp @@ -27,8 +27,11 @@ #include "Commands.h" #include "android-base/properties.h" #include "idmap2/CommandLineOptions.h" +#include "idmap2/CommandUtils.h" #include "idmap2/FileUtils.h" #include "idmap2/Idmap.h" +#include "idmap2/Policies.h" +#include "idmap2/PolicyUtils.h" #include "idmap2/ResourceUtils.h" #include "idmap2/Result.h" #include "idmap2/SysTrace.h" @@ -48,6 +51,7 @@ using android::idmap2::policy::kPolicyVendor; using android::idmap2::utils::ExtractOverlayManifestInfo; using android::idmap2::utils::FindFiles; using android::idmap2::utils::OverlayManifestInfo; +using android::idmap2::utils::PoliciesToBitmaskResult; using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; @@ -215,7 +219,15 @@ Result Scan(const std::vector& args) { std::stringstream stream; for (const auto& overlay : interesting_apks) { - if (!Verify(std::vector({"--idmap-path", overlay.idmap_path}))) { + const auto policy_bitmask = PoliciesToBitmaskResult(overlay.policies); + if (!policy_bitmask) { + LOG(WARNING) << "failed to create idmap for overlay apk path \"" << overlay.apk_path + << "\": " << policy_bitmask.GetErrorMessage(); + continue; + } + + if (!Verify(overlay.idmap_path, target_apk_path, overlay.apk_path, *policy_bitmask, + !overlay.ignore_overlayable)) { std::vector create_args = {"--target-apk-path", target_apk_path, "--overlay-apk-path", overlay.apk_path, "--idmap-path", overlay.idmap_path}; diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index a93184ff4787593402e11978b6bda374d869c8bd..f95b73f17222ff6cc3a1fc90ffe4ebd36762ee82 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -33,16 +33,19 @@ #include "idmap2/BinaryStreamVisitor.h" #include "idmap2/FileUtils.h" #include "idmap2/Idmap.h" +#include "idmap2/Result.h" #include "idmap2/SysTrace.h" #include "idmap2/ZipFile.h" #include "utils/String8.h" using android::IPCThreadState; +using android::base::StringPrintf; using android::binder::Status; using android::idmap2::BinaryStreamVisitor; using android::idmap2::GetPackageCrc; using android::idmap2::Idmap; using android::idmap2::IdmapHeader; +using android::idmap2::ZipFile; using android::idmap2::utils::kIdmapCacheDir; using android::idmap2::utils::kIdmapFilePermissionMask; using android::idmap2::utils::UidHasWriteAccessToPath; @@ -66,6 +69,21 @@ PolicyBitmask ConvertAidlArgToPolicyBitmask(int32_t arg) { return static_cast(arg); } +Status GetCrc(const std::string& apk_path, uint32_t* out_crc) { + const auto zip = ZipFile::Open(apk_path); + if (!zip) { + return error(StringPrintf("failed to open apk %s", apk_path.c_str())); + } + + const auto crc = GetPackageCrc(*zip); + if (!crc) { + return error(crc.GetErrorMessage()); + } + + *out_crc = *crc; + return ok(); +} + } // namespace namespace android::os { @@ -98,12 +116,12 @@ Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path, } Status Idmap2Service::verifyIdmap(const std::string& target_apk_path, - const std::string& overlay_apk_path, - int32_t fulfilled_policies ATTRIBUTE_UNUSED, - bool enforce_overlayable ATTRIBUTE_UNUSED, - int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) { + const std::string& overlay_apk_path, int32_t fulfilled_policies, + bool enforce_overlayable, int32_t user_id ATTRIBUTE_UNUSED, + bool* _aidl_return) { SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path; assert(_aidl_return); + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); std::ifstream fin(idmap_path); const std::unique_ptr header = IdmapHeader::FromBinaryStream(fin); @@ -113,35 +131,36 @@ Status Idmap2Service::verifyIdmap(const std::string& target_apk_path, return error("failed to parse idmap header"); } - if (strcmp(header->GetTargetPath().data(), target_apk_path.data()) != 0) { - *_aidl_return = false; - return ok(); - } - - if (target_apk_path != kFrameworkPath) { - *_aidl_return = (bool) header->IsUpToDate(); + uint32_t target_crc; + if (target_apk_path == kFrameworkPath && android_crc_) { + target_crc = *android_crc_; } else { - if (!android_crc_) { - // Loading the framework zip can take several milliseconds. Cache the crc of the framework - // resource APK to reduce repeated work during boot. - const auto target_zip = idmap2::ZipFile::Open(target_apk_path); - if (!target_zip) { - return error(base::StringPrintf("failed to open target %s", target_apk_path.c_str())); - } - - const auto target_crc = GetPackageCrc(*target_zip); - if (!target_crc) { - return error(target_crc.GetErrorMessage()); - } - - android_crc_ = *target_crc; + auto target_crc_status = GetCrc(target_apk_path, &target_crc); + if (!target_crc_status.isOk()) { + *_aidl_return = false; + return target_crc_status; + } + + // Loading the framework zip can take several milliseconds. Cache the crc of the framework + // resource APK to reduce repeated work during boot. + if (target_apk_path == kFrameworkPath) { + android_crc_ = target_crc; } + } - *_aidl_return = (bool) header->IsUpToDate(android_crc_.value()); + uint32_t overlay_crc; + auto overlay_crc_status = GetCrc(overlay_apk_path, &overlay_crc); + if (!overlay_crc_status.isOk()) { + *_aidl_return = false; + return overlay_crc_status; } - // TODO(b/119328308): Check that the set of fulfilled policies of the overlay has not changed - return ok(); + auto up_to_date = + header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), target_crc, overlay_crc, + ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable); + + *_aidl_return = static_cast(up_to_date); + return *_aidl_return ? ok() : error(up_to_date.GetErrorMessage()); } Status Idmap2Service::createIdmap(const std::string& target_apk_path, diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h index 77a7b30a230e0616d304406d544eead0773f6bef..0f05592b70f3c695610eb6fc43da119420588eb9 100644 --- a/cmds/idmap2/include/idmap2/Idmap.h +++ b/cmds/idmap2/include/idmap2/Idmap.h @@ -117,6 +117,14 @@ class IdmapHeader { return overlay_crc_; } + inline uint32_t GetFulfilledPolicies() const { + return fulfilled_policies_; + } + + bool GetEnforceOverlayable() const { + return enforce_overlayable_; + } + inline StringPiece GetTargetPath() const { return StringPiece(target_path_); } @@ -132,8 +140,11 @@ class IdmapHeader { // Invariant: anytime the idmap data encoding is changed, the idmap version // field *must* be incremented. Because of this, we know that if the idmap // header is up-to-date the entire file is up-to-date. - Result IsUpToDate() const; - Result IsUpToDate(uint32_t target_crc_) const; + Result IsUpToDate(const char* target_path, const char* overlay_path, + PolicyBitmask fulfilled_policies, bool enforce_overlayable) const; + Result IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc, + uint32_t overlay_crc, PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const; void accept(Visitor* v) const; @@ -145,6 +156,8 @@ class IdmapHeader { uint32_t version_; uint32_t target_crc_; uint32_t overlay_crc_; + uint32_t fulfilled_policies_; + bool enforce_overlayable_; char target_path_[kIdmapStringLength]; char overlay_path_[kIdmapStringLength]; std::string debug_info_; diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp index 362dcb36007a409aff1a0f898c3e869766f5505e..255212ad4c66b5e32f3b4e7be71c77476d52f822 100644 --- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp +++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp @@ -66,6 +66,8 @@ void BinaryStreamVisitor::visit(const IdmapHeader& header) { Write32(header.GetVersion()); Write32(header.GetTargetCrc()); Write32(header.GetOverlayCrc()); + Write32(header.GetFulfilledPolicies()); + Write8(static_cast(header.GetEnforceOverlayable())); WriteString256(header.GetTargetPath()); WriteString256(header.GetOverlayPath()); WriteString(header.GetDebugInfo()); diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 706b842b3b4782c96b27cb40264cd1cae37668fd..23c25a7089decbf7b99d34929bcd13cd8da263aa 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -112,14 +112,17 @@ Result GetPackageCrc(const ZipFile& zip) { std::unique_ptr IdmapHeader::FromBinaryStream(std::istream& stream) { std::unique_ptr idmap_header(new IdmapHeader()); - + uint8_t enforce_overlayable; if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) || !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) || + !Read32(stream, &idmap_header->fulfilled_policies_) || !Read8(stream, &enforce_overlayable) || !ReadString256(stream, idmap_header->target_path_) || !ReadString256(stream, idmap_header->overlay_path_)) { return nullptr; } + idmap_header->enforce_overlayable_ = static_cast(enforce_overlayable); + auto debug_str = ReadString(stream); if (!debug_str) { return nullptr; @@ -129,21 +132,37 @@ std::unique_ptr IdmapHeader::FromBinaryStream(std::istream& s return std::move(idmap_header); } -Result IdmapHeader::IsUpToDate() const { - const std::unique_ptr target_zip = ZipFile::Open(target_path_); +Result IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path, + PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const { + const std::unique_ptr target_zip = ZipFile::Open(target_path); if (!target_zip) { - return Error("failed to open target %s", GetTargetPath().to_string().c_str()); + return Error("failed to open target %s", target_path); } - Result target_crc = GetPackageCrc(*target_zip); + const Result target_crc = GetPackageCrc(*target_zip); if (!target_crc) { return Error("failed to get target crc"); } - return IsUpToDate(*target_crc); + const std::unique_ptr overlay_zip = ZipFile::Open(overlay_path); + if (!overlay_zip) { + return Error("failed to overlay target %s", overlay_path); + } + + const Result overlay_crc = GetPackageCrc(*overlay_zip); + if (!overlay_crc) { + return Error("failed to get overlay crc"); + } + + return IsUpToDate(target_path, overlay_path, *target_crc, *overlay_crc, fulfilled_policies, + enforce_overlayable); } -Result IdmapHeader::IsUpToDate(uint32_t target_crc) const { +Result IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path, + uint32_t target_crc, uint32_t overlay_crc, + PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const { if (magic_ != kIdmapMagic) { return Error("bad magic: actual 0x%08x, expected 0x%08x", magic_, kIdmapMagic); } @@ -157,19 +176,29 @@ Result IdmapHeader::IsUpToDate(uint32_t target_crc) const { target_crc); } - const std::unique_ptr overlay_zip = ZipFile::Open(overlay_path_); - if (!overlay_zip) { - return Error("failed to open overlay %s", GetOverlayPath().to_string().c_str()); + if (overlay_crc_ != overlay_crc) { + return Error("bad overlay crc: idmap version 0x%08x, file system version 0x%08x", overlay_crc_, + overlay_crc); } - Result overlay_crc = GetPackageCrc(*overlay_zip); - if (!overlay_crc) { - return Error("failed to get overlay crc"); + if (fulfilled_policies_ != fulfilled_policies) { + return Error("bad fulfilled policies: idmap version 0x%08x, file system version 0x%08x", + fulfilled_policies, fulfilled_policies_); } - if (overlay_crc_ != *overlay_crc) { - return Error("bad overlay crc: idmap version 0x%08x, file system version 0x%08x", overlay_crc_, - *overlay_crc); + if (enforce_overlayable != enforce_overlayable_) { + return Error("bad enforce overlayable: idmap version %s, file system version %s", + enforce_overlayable ? "true" : "false", enforce_overlayable_ ? "true" : "false"); + } + + if (strcmp(target_path, target_path_) != 0) { + return Error("bad target path: idmap version %s, file system version %s", target_path, + target_path_); + } + + if (strcmp(overlay_path, overlay_path_) != 0) { + return Error("bad overlay path: idmap version %s, file system version %s", overlay_path, + overlay_path_); } return Unit{}; @@ -320,6 +349,9 @@ Result> Idmap::FromApkAssets(const ApkAssets& targe } header->overlay_crc_ = *crc; + header->fulfilled_policies_ = fulfilled_policies; + header->enforce_overlayable_ = enforce_overlayable; + if (target_apk_path.size() > sizeof(header->target_path_)) { return Error("target apk path \"%s\" longer than maximum size %zu", target_apk_path.c_str(), sizeof(header->target_path_)); diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp index 751c60c4add4cc84edf2254659b5301b47c62b96..3f62a2ae20294bf10de2f30b0fbe584f0d6fe5e5 100644 --- a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp +++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp @@ -23,10 +23,12 @@ #include "android-base/macros.h" #include "android-base/stringprintf.h" #include "androidfw/ApkAssets.h" +#include "idmap2/PolicyUtils.h" #include "idmap2/ResourceUtils.h" #include "idmap2/Result.h" using android::ApkAssets; +using android::idmap2::policy::PoliciesToDebugString; namespace { @@ -39,9 +41,6 @@ size_t StringSizeWhenEncoded(const std::string& s) { namespace android::idmap2 { -// verbatim copy fomr PrettyPrintVisitor.cpp, move to common utils -#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry)) - void RawPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) { } @@ -50,6 +49,9 @@ void RawPrintVisitor::visit(const IdmapHeader& header) { print(header.GetVersion(), "version"); print(header.GetTargetCrc(), "target crc"); print(header.GetOverlayCrc(), "overlay crc"); + print(header.GetFulfilledPolicies(), "fulfilled policies: %s", + PoliciesToDebugString(header.GetFulfilledPolicies()).c_str()); + print(static_cast(header.GetEnforceOverlayable()), "enforce overlayable"); print(header.GetTargetPath().to_string(), kIdmapStringLength, "target path"); print(header.GetOverlayPath().to_string(), kIdmapStringLength, "overlay path"); print("...", StringSizeWhenEncoded(header.GetDebugInfo()), "debug info"); diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp index 44acbcaf8ace236b88845e363af2555329f3534b..34589a1c39dcce3669047647a3f702fd2f2288fe 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -61,8 +61,7 @@ Result CheckOverlayable(const LoadedPackage& target_package, const ResourceId& target_resource) { static constexpr const PolicyBitmask sDefaultPolicies = PolicyFlags::ODM_PARTITION | PolicyFlags::OEM_PARTITION | PolicyFlags::SYSTEM_PARTITION | - PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE | - PolicyFlags::ACTOR_SIGNATURE; + PolicyFlags::VENDOR_PARTITION | PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE; // If the resource does not have an overlayable definition, allow the resource to be overlaid if // the overlay is preinstalled or signed with the same signature as the target. @@ -292,13 +291,6 @@ Result ResourceMapping::FromApkAssets(const ApkAssets& target_a const PolicyBitmask& fulfilled_policies, bool enforce_overlayable, LogInfo& log_info) { - if (enforce_overlayable) { - log_info.Info(LogMessage() << "fulfilled_policies=" - << ConcatPolicies(BitmaskToPolicies(fulfilled_policies)) - << " enforce_overlayable=" - << (enforce_overlayable ? "true" : "false")); - } - AssetManager2 target_asset_manager; if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true /* invalidate_caches */, false /* filter_incompatible_configs*/)) { diff --git a/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h b/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h index 4973b7638d10b71f2c4aa593cf66de2d17f97e31..5bd353af4ad3b71af5aab51af6150215dc8570fd 100644 --- a/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h +++ b/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h @@ -21,9 +21,12 @@ #include #include +#include "android-base/stringprintf.h" #include "androidfw/ResourceTypes.h" #include "androidfw/StringPiece.h" +using android::base::StringPrintf; + using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; @@ -48,6 +51,29 @@ inline static const std::array, 8> kPolicySt {kPolicySystem, PolicyFlags::SYSTEM_PARTITION}, {kPolicyVendor, PolicyFlags::VENDOR_PARTITION}, }; + +inline static std::string PoliciesToDebugString(PolicyBitmask policies) { + std::string str; + uint32_t remaining = policies; + for (auto const& policy : kPolicyStringToFlag) { + if ((policies & policy.second) != policy.second) { + continue; + } + if (!str.empty()) { + str.append("|"); + } + str.append(policy.first.data()); + remaining &= ~policy.second; + } + if (remaining != 0) { + if (!str.empty()) { + str.append("|"); + } + str.append(StringPrintf("0x%08x", remaining)); + } + return !str.empty() ? str : "none"; +} + } // namespace android::idmap2::policy #endif // IDMAP2_INCLUDE_IDMAP2_POLICIES_H_ diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp index db4778c8ee095eae1b1079dd025471742c2a37a2..5fea7bcdaac59c304cb4ef83b740e07ebdcee6c9 100644 --- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp +++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp @@ -48,6 +48,11 @@ TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) { ASSERT_TRUE(result2); const auto idmap2 = std::move(*result2); + ASSERT_EQ(idmap1->GetHeader()->GetFulfilledPolicies(), + idmap2->GetHeader()->GetFulfilledPolicies()); + ASSERT_EQ(idmap1->GetHeader()->GetEnforceOverlayable(), + idmap2->GetHeader()->GetEnforceOverlayable()); + ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath()); ASSERT_EQ(idmap1->GetHeader()->GetTargetCrc(), idmap2->GetHeader()->GetTargetCrc()); ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath()); ASSERT_EQ(idmap1->GetData().size(), 1U); diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp index 87da36c01192a33d19b59ce52a1f4ca419ee6352..6fab5e0f8ae121a457e7ffeabd922fe7582e0f5a 100644 --- a/cmds/idmap2/tests/IdmapTests.cpp +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -62,9 +62,11 @@ TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) { std::unique_ptr header = IdmapHeader::FromBinaryStream(stream); ASSERT_THAT(header, NotNull()); ASSERT_EQ(header->GetMagic(), 0x504d4449U); - ASSERT_EQ(header->GetVersion(), 0x03U); + ASSERT_EQ(header->GetVersion(), 0x04U); ASSERT_EQ(header->GetTargetCrc(), 0x1234U); ASSERT_EQ(header->GetOverlayCrc(), 0x5678U); + ASSERT_EQ(header->GetFulfilledPolicies(), 0x11); + ASSERT_EQ(header->GetEnforceOverlayable(), true); ASSERT_EQ(header->GetTargetPath().to_string(), "targetX.apk"); ASSERT_EQ(header->GetOverlayPath().to_string(), "overlayX.apk"); ASSERT_EQ(header->GetDebugInfo(), "debug"); @@ -73,7 +75,7 @@ TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) { TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) { std::string raw(reinterpret_cast(idmap_raw_data), idmap_raw_data_len); // overwrite the target path string, including the terminating null, with '.' - for (size_t i = 0x10; i < 0x110; i++) { + for (size_t i = 0x15; i < 0x115; i++) { raw[i] = '.'; } std::istringstream stream(raw); @@ -82,7 +84,7 @@ TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) { } TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) { - const size_t offset = 0x21c; + const size_t offset = 0x221; std::string raw(reinterpret_cast(idmap_raw_data + offset), idmap_raw_data_len - offset); std::istringstream stream(raw); @@ -94,7 +96,7 @@ TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) { } TEST(IdmapTests, CreateIdmapDataFromBinaryStream) { - const size_t offset = 0x21c; + const size_t offset = 0x221; std::string raw(reinterpret_cast(idmap_raw_data + offset), idmap_raw_data_len - offset); std::istringstream stream(raw); @@ -128,9 +130,11 @@ TEST(IdmapTests, CreateIdmapFromBinaryStream) { ASSERT_THAT(idmap->GetHeader(), NotNull()); ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U); - ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x03U); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x04U); ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234U); ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678U); + ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), 0x11); + ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true); ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "targetX.apk"); ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlayX.apk"); @@ -180,9 +184,11 @@ TEST(IdmapTests, CreateIdmapHeaderFromApkAssets) { ASSERT_THAT(idmap->GetHeader(), NotNull()); ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U); - ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x03U); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x04U); ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), android::idmap2::TestConstants::TARGET_CRC); ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), android::idmap2::TestConstants::OVERLAY_CRC); + ASSERT_EQ(idmap->GetHeader()->GetFulfilledPolicies(), PolicyFlags::PUBLIC); + ASSERT_EQ(idmap->GetHeader()->GetEnforceOverlayable(), true); ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path); ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path); } @@ -389,7 +395,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { std::unique_ptr header = IdmapHeader::FromBinaryStream(stream); ASSERT_THAT(header, NotNull()); - ASSERT_TRUE(header->IsUpToDate()); + ASSERT_TRUE(header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); // magic: bytes (0x0, 0x03) std::string bad_magic_string(stream.str()); @@ -402,7 +409,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_magic_stream); ASSERT_THAT(bad_magic_header, NotNull()); ASSERT_NE(header->GetMagic(), bad_magic_header->GetMagic()); - ASSERT_FALSE(bad_magic_header->IsUpToDate()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); // version: bytes (0x4, 0x07) std::string bad_version_string(stream.str()); @@ -415,7 +423,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_version_stream); ASSERT_THAT(bad_version_header, NotNull()); ASSERT_NE(header->GetVersion(), bad_version_header->GetVersion()); - ASSERT_FALSE(bad_version_header->IsUpToDate()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); // target crc: bytes (0x8, 0xb) std::string bad_target_crc_string(stream.str()); @@ -428,7 +437,8 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_target_crc_stream); ASSERT_THAT(bad_target_crc_header, NotNull()); ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc()); - ASSERT_FALSE(bad_target_crc_header->IsUpToDate()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); // overlay crc: bytes (0xc, 0xf) std::string bad_overlay_crc_string(stream.str()); @@ -441,27 +451,55 @@ TEST(IdmapTests, IdmapHeaderIsUpToDate) { IdmapHeader::FromBinaryStream(bad_overlay_crc_stream); ASSERT_THAT(bad_overlay_crc_header, NotNull()); ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc()); - ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate()); - - // target path: bytes (0x10, 0x10f) + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + + // fulfilled policy: bytes (0x10, 0x13) + std::string bad_policy_string(stream.str()); + bad_policy_string[0x10] = '.'; + bad_policy_string[0x11] = '.'; + bad_policy_string[0x12] = '.'; + bad_policy_string[0x13] = '.'; + std::stringstream bad_policy_stream(bad_policy_string); + std::unique_ptr bad_policy_header = + IdmapHeader::FromBinaryStream(bad_policy_stream); + ASSERT_THAT(bad_policy_header, NotNull()); + ASSERT_NE(header->GetFulfilledPolicies(), bad_policy_header->GetFulfilledPolicies()); + ASSERT_FALSE(bad_policy_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + + // enforce overlayable: bytes (0x14) + std::string bad_enforce_string(stream.str()); + bad_enforce_string[0x14] = '\0'; + std::stringstream bad_enforce_stream(bad_enforce_string); + std::unique_ptr bad_enforce_header = + IdmapHeader::FromBinaryStream(bad_enforce_stream); + ASSERT_THAT(bad_enforce_header, NotNull()); + ASSERT_NE(header->GetEnforceOverlayable(), bad_enforce_header->GetEnforceOverlayable()); + ASSERT_FALSE(bad_enforce_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); + + // target path: bytes (0x15, 0x114) std::string bad_target_path_string(stream.str()); - bad_target_path_string[0x10] = '\0'; + bad_target_path_string[0x15] = '\0'; std::stringstream bad_target_path_stream(bad_target_path_string); std::unique_ptr bad_target_path_header = IdmapHeader::FromBinaryStream(bad_target_path_stream); ASSERT_THAT(bad_target_path_header, NotNull()); ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath()); - ASSERT_FALSE(bad_target_path_header->IsUpToDate()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); - // overlay path: bytes (0x110, 0x20f) + // overlay path: bytes (0x115, 0x214) std::string bad_overlay_path_string(stream.str()); - bad_overlay_path_string[0x110] = '\0'; + bad_overlay_path_string[0x115] = '\0'; std::stringstream bad_overlay_path_stream(bad_overlay_path_string); std::unique_ptr bad_overlay_path_header = IdmapHeader::FromBinaryStream(bad_overlay_path_stream); ASSERT_THAT(bad_overlay_path_header, NotNull()); ASSERT_NE(header->GetOverlayPath(), bad_overlay_path_header->GetOverlayPath()); - ASSERT_FALSE(bad_overlay_path_header->IsUpToDate()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), + PolicyFlags::PUBLIC, /* enforce_overlayable */ true)); } class TestVisitor : public Visitor { diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp index 5c5c81edee90d500739524bebedd6815a22327b3..b268d5add14130ce1771febe0077f9637f44d660 100644 --- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp +++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp @@ -43,6 +43,8 @@ namespace android::idmap2 { << str << "--------"; \ } while (0) +#define ADDRESS "[0-9a-f]{8}: " + TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { fclose(stderr); // silence expected warnings @@ -62,15 +64,16 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { RawPrintVisitor visitor(stream); (*idmap)->accept(&visitor); -#define ADDRESS "[0-9a-f]{8}: " ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str()); - ASSERT_CONTAINS_REGEX(ADDRESS "00000003 version\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000004 version\n", stream.str()); ASSERT_CONTAINS_REGEX( StringPrintf(ADDRESS "%s target crc\n", android::idmap2::TestConstants::TARGET_CRC_STRING), stream.str()); ASSERT_CONTAINS_REGEX( StringPrintf(ADDRESS "%s overlay crc\n", android::idmap2::TestConstants::OVERLAY_CRC_STRING), stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000001 fulfilled policies: public\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 01 enforce overlayable\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "00000004 target entry count\n", stream.str()); @@ -83,7 +86,6 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 value: integer/int1\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 overlay id: integer/int1\n", stream.str()); ASSERT_CONTAINS_REGEX(ADDRESS "7f010000 target id: integer/int1\n", stream.str()); -#undef ADDRESS } TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { @@ -99,22 +101,23 @@ TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { RawPrintVisitor visitor(stream); (*idmap)->accept(&visitor); - ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos); - ASSERT_NE(stream.str().find("00000004: 00000003 version\n"), std::string::npos); - ASSERT_NE(stream.str().find("00000008: 00001234 target crc\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000000c: 00005678 overlay crc\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000021c: 7f target package id\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000021d: 7f overlay package id\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000021e: 00000003 target entry count\n"), std::string::npos); - ASSERT_NE(stream.str().find("00000222: 00000003 overlay entry count\n"), std::string::npos); - ASSERT_NE(stream.str().find("00000226: 00000000 string pool index offset\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000022a: 00000000 string pool byte length\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000022e: 7f020000 target id\n"), std::string::npos); - ASSERT_NE(stream.str().find("00000232: 01 type: reference\n"), std::string::npos); - ASSERT_NE(stream.str().find("00000233: 7f020000 value\n"), std::string::npos); - - ASSERT_NE(stream.str().find("00000249: 7f020000 overlay id\n"), std::string::npos); - ASSERT_NE(stream.str().find("0000024d: 7f020000 target id\n"), std::string::npos); + ASSERT_CONTAINS_REGEX(ADDRESS "504d4449 magic\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000004 version\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00001234 target crc\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00005678 overlay crc\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000011 fulfilled policies: public|signature\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 01 enforce overlayable\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 7f target package id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 7f overlay package id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000003 target entry count\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000003 overlay entry count\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000000 string pool index offset\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "00000000 string pool byte length\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS " 01 type: reference\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 value\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 overlay id\n", stream.str()); + ASSERT_CONTAINS_REGEX(ADDRESS "7f020000 target id\n", stream.str()); } } // namespace android::idmap2 diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp index 5754eaf078a98fc7cb5dd9bf6bb509aa8d91e011..de039f440e33f1d73c1ed3e1125dbd8bad3309e1 100644 --- a/cmds/idmap2/tests/ResourceMappingTests.cpp +++ b/cmds/idmap2/tests/ResourceMappingTests.cpp @@ -287,26 +287,66 @@ TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTarget R::overlay::string::str4, false /* rewrite */)); } - -// Overlays that are pre-installed or are signed with the same signature as the target/actor can +// Overlays that are neither pre-installed nor signed with the same signature as the target cannot // overlay packages that have not defined overlayable resources. -TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) { - constexpr PolicyBitmask kDefaultPolicies = - PolicyFlags::SIGNATURE | PolicyFlags::ACTOR_SIGNATURE | PolicyFlags::PRODUCT_PARTITION | - PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION | PolicyFlags::ODM_PARTITION | - PolicyFlags::OEM_PARTITION; +TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) { + auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk", + "/overlay/overlay-no-name.apk", PolicyFlags::PUBLIC, + /* enforce_overlayable */ true); + + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U); +} - for (PolicyBitmask policy = 1U << (sizeof(PolicyBitmask) * 8 - 1); policy > 0; - policy = policy >> 1U) { +// Overlays that are pre-installed or are signed with the same signature as the target can overlay +// packages that have not defined overlayable resources. +TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) { + auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void { auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk", "/system-overlay-invalid/system-overlay-invalid.apk", - policy, /* enforce_overlayable */ true); - ASSERT_TRUE(resources) << resources.GetErrorMessage(); + fulfilled_policies, + /* enforce_overlayable */ true); - const size_t expected_overlaid = (policy & kDefaultPolicies) != 0 ? 10U : 0U; - ASSERT_EQ(expected_overlaid, resources->GetTargetToOverlayMap().size()) - << "Incorrect number of resources overlaid through policy " << policy; - } + ASSERT_TRUE(resources) << resources.GetErrorMessage(); + auto& res = *resources; + ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 10U); + ASSERT_RESULT(MappingExists(res, R::target::string::not_overlayable, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::not_overlayable, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::other, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::other, false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_actor, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_actor, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_odm, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_odm, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_oem, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_oem, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_product, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_product, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_public, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_public, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_signature, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_signature, + false /* rewrite */)); + ASSERT_RESULT(MappingExists(res, R::target::string::policy_system, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_system, + false /* rewrite */)); + ASSERT_RESULT(MappingExists( + res, R::target::string::policy_system_vendor, Res_value::TYPE_REFERENCE, + R::system_overlay_invalid::string::policy_system_vendor, false /* rewrite */)); + }; + + CheckEntries(PolicyFlags::SIGNATURE); + CheckEntries(PolicyFlags::PRODUCT_PARTITION); + CheckEntries(PolicyFlags::SYSTEM_PARTITION); + CheckEntries(PolicyFlags::VENDOR_PARTITION); + CheckEntries(PolicyFlags::ODM_PARTITION); + CheckEntries(PolicyFlags::OEM_PARTITION); } } // namespace android::idmap2 diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h index e899589c7e61035116f6b4435b4a0f4953c5cffe..b599dcb0069ab33e986db07966df63f4b4dcbf56 100644 --- a/cmds/idmap2/tests/TestHelpers.h +++ b/cmds/idmap2/tests/TestHelpers.h @@ -30,7 +30,7 @@ const unsigned char idmap_raw_data[] = { 0x49, 0x44, 0x4d, 0x50, // 0x4: version - 0x03, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, // 0x8: target crc 0x34, 0x12, 0x00, 0x00, @@ -38,7 +38,13 @@ const unsigned char idmap_raw_data[] = { // 0xc: overlay crc 0x78, 0x56, 0x00, 0x00, - // 0x10: target path "targetX.apk" + // 0x10: fulfilled policies + 0x11, 0x00, 0x00, 0x00, + + // 0x14: enforce overlayable + 0x01, + + // 0x15: target path "targetX.apk" 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -56,7 +62,7 @@ const unsigned char idmap_raw_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 0x110: overlay path "overlayX.apk" + // 0x115: overlay path "overlayX.apk" 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x58, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -74,7 +80,7 @@ const unsigned char idmap_raw_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 0x210: debug string + // 0x215: debug string // string length, including terminating null 0x08, 0x00, 0x00, 0x00, @@ -82,63 +88,63 @@ const unsigned char idmap_raw_data[] = { 0x64, 0x65, 0x62, 0x75, 0x67, 0x00, 0x00, 0x00, // DATA HEADER - // 0x21c: target_package_id + // 0x221: target_package_id 0x7f, - // 0x21d: overlay_package_id + // 0x222: overlay_package_id 0x7f, - // 0x21e: target_entry_count + // 0x223: target_entry_count 0x03, 0x00, 0x00, 0x00, - // 0x222: overlay_entry_count + // 0x227: overlay_entry_count 0x03, 0x00, 0x00, 0x00, - // 0x226: string_pool_offset + // 0x22b: string_pool_offset 0x00, 0x00, 0x00, 0x00, - // 0x22a: string_pool_byte_length + // 0x22f: string_pool_byte_length 0x00, 0x00, 0x00, 0x00, // TARGET ENTRIES - // 0x22e: 0x7f020000 + // 0x233: 0x7f020000 0x00, 0x00, 0x02, 0x7f, - // 0x232: TYPE_REFERENCE + // 0x237: TYPE_REFERENCE 0x01, - // 0x233: 0x7f020000 + // 0x238: 0x7f020000 0x00, 0x00, 0x02, 0x7f, - // 0x237: 0x7f030000 + // 0x23c: 0x7f030000 0x00, 0x00, 0x03, 0x7f, - // 0x23b: TYPE_REFERENCE + // 0x240: TYPE_REFERENCE 0x01, - // 0x23c: 0x7f030000 + // 0x241: 0x7f030000 0x00, 0x00, 0x03, 0x7f, - // 0x240: 0x7f030002 + // 0x245: 0x7f030002 0x02, 0x00, 0x03, 0x7f, - // 0x244: TYPE_REFERENCE + // 0x249: TYPE_REFERENCE 0x01, - // 0x245: 0x7f030001 + // 0x24a: 0x7f030001 0x01, 0x00, 0x03, 0x7f, // OVERLAY ENTRIES - // 0x249: 0x7f020000 -> 0x7f020000 + // 0x24e: 0x7f020000 -> 0x7f020000 0x00, 0x00, 0x02, 0x7f, 0x00, 0x00, 0x02, 0x7f, - // 0x251: 0x7f030000 -> 0x7f030000 + // 0x256: 0x7f030000 -> 0x7f030000 0x00, 0x00, 0x03, 0x7f, 0x00, 0x00, 0x03, 0x7f, - // 0x259: 0x7f030001 -> 0x7f030002 + // 0x25e: 0x7f030001 -> 0x7f030002 0x01, 0x00, 0x03, 0x7f, 0x02, 0x00, 0x03, 0x7f}; -const unsigned int idmap_raw_data_len = 0x261; +const unsigned int idmap_raw_data_len = 0x266; std::string GetTestDataPath(); diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 61e5eb07130c3ae2213ce4a658ac7c51fce60e6b..33e764988b7516461fa5fb476e627d616db5fc3f 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -477,14 +477,15 @@ status_t TextDumpsysSection::Execute(ReportWriter* writer) const { // Run dumping thread const uint64_t start = Nanotime(); - std::thread worker([&]() { + std::thread worker([write_fd = std::move(dumpPipe.writeFd()), service = std::move(service), + this]() mutable { // Don't crash the service if writing to a closed pipe (may happen if dumping times out) signal(SIGPIPE, sigpipe_handler); - status_t err = service->dump(dumpPipe.writeFd().get(), mArgs); + status_t err = service->dump(write_fd.get(), this->mArgs); if (err != OK) { ALOGW("[%s] dump thread failed. Error: %s", this->name.string(), strerror(-err)); } - dumpPipe.writeFd().reset(); + write_fd.reset(); }); // Collect dump content diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 3dbe4139502458b45e472efdf6b50aa101902a72..0617eb6c0e6633b75564e9e7c869591b36def9de 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -116,7 +116,6 @@ cc_defaults { "libcutils", "libgtest_prod", "libprotoutil", - "libstatsmetadata", "libstatslog_statsd", "libsysutils", "libutils", @@ -129,51 +128,6 @@ cc_defaults { ], } -// ================ -// libstatsmetadata -// ================ - -genrule { - name: "atoms_info.h", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --atomsInfoHeader $(genDir)/atoms_info.h", - out: [ - "atoms_info.h", - ], -} - -genrule { - name: "atoms_info.cpp", - tools: ["stats-log-api-gen"], - cmd: "$(location stats-log-api-gen) --atomsInfoCpp $(genDir)/atoms_info.cpp", - out: [ - "atoms_info.cpp", - ], -} - -cc_library_static { - name: "libstatsmetadata", - host_supported: true, - generated_sources: [ - "atoms_info.cpp", - ], - generated_headers: [ - "atoms_info.h", - ], - cflags: [ - "-Wall", - "-Werror", - ], - export_generated_headers: [ - "atoms_info.h", - ], - apex_available: [ - //TODO(b/149782403): Remove this once statsd no longer links against libstatsmetadata - "com.android.os.statsd", - "test_com.android.os.statsd", - ], -} - genrule { name: "statslog_statsd.h", tools: ["stats-log-api-gen"], diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 095dd1e9be5929df63f061f0163081226e778b1c..e7b32c56551a09b08ea8520ff67033e2ca557f7a 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -529,7 +529,9 @@ void StatsLogProcessor::OnConfigUpdatedLocked( VLOG("StatsdConfig valid"); } else { // If there is any error in the config, don't use it. + // Remove any existing config with the same key. ALOGE("StatsdConfig NOT valid"); + mMetricsManagers.erase(key); } } diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 7090bd46635dcbc217e17afd7d63761b341a4f74..23f2584655b0d3859f0d8111d97f783f785a1921 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -284,6 +284,7 @@ private: FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize); FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast); FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge); + FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved); FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead); FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot); FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBootMultipleActivations); diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 47bab2947aaf0638f2fa2e7dbf2e432f283e8c18..6f952f637506c903b4dff14e4d9b3b9e1998a9ee 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -267,8 +267,11 @@ void StatsService::dumpIncidentSection(int out) { for (const ConfigKey& configKey : mConfigManager->GetAllConfigKeys()) { uint64_t reportsListToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_REPORTS_LIST); + // Don't include the current bucket to avoid skipping buckets. + // If we need to include the current bucket later, consider changing to NO_TIME_CONSTRAINTS + // or other alternatives to avoid skipping buckets for pulled metrics. mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), - true /* includeCurrentBucket */, false /* erase_data */, + false /* includeCurrentBucket */, false /* erase_data */, ADB_DUMP, FAST, &proto); diff --git a/cmds/statsd/src/anomaly/AlarmTracker.cpp b/cmds/statsd/src/anomaly/AlarmTracker.cpp index 5722f923d11ec9c84abbd340ec871d821d2cfb13..6d9beb8f718de94d6cb3d5b8d0fb2367dc9d9704 100644 --- a/cmds/statsd/src/anomaly/AlarmTracker.cpp +++ b/cmds/statsd/src/anomaly/AlarmTracker.cpp @@ -60,11 +60,11 @@ void AlarmTracker::addSubscription(const Subscription& subscription) { } int64_t AlarmTracker::findNextAlarmSec(int64_t currentTimeSec) { - if (currentTimeSec <= mAlarmSec) { + if (currentTimeSec < mAlarmSec) { return mAlarmSec; } int64_t periodsForward = - ((currentTimeSec - mAlarmSec) * MS_PER_SEC - 1) / mAlarmConfig.period_millis() + 1; + ((currentTimeSec - mAlarmSec) * MS_PER_SEC) / mAlarmConfig.period_millis() + 1; return mAlarmSec + periodsForward * mAlarmConfig.period_millis() / MS_PER_SEC; } diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto index 8527185d38910adab4a8f42f120fd32cd67cac0a..ff5717e4fa7868fb6a2858a1fc61f032e755f01e 100644 --- a/cmds/statsd/src/atom_field_options.proto +++ b/cmds/statsd/src/atom_field_options.proto @@ -110,8 +110,6 @@ extend google.protobuf.FieldOptions { optional LogMode log_mode = 50002 [default = MODE_AUTOMATIC]; - optional bool allow_from_any_uid = 50003 [default = false]; - repeated string module = 50004; optional bool truncate_timestamp = 50005 [default = false]; diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 36a8b2cfc084149bfca3ce12d143102c4ac15f30..7a016522d597c9904c4211cb0a5660793959a78f 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -43,6 +43,7 @@ import "frameworks/base/core/proto/android/server/location/enums.proto"; import "frameworks/base/core/proto/android/service/procstats_enum.proto"; import "frameworks/base/core/proto/android/service/usb.proto"; import "frameworks/base/core/proto/android/stats/connectivity/network_stack.proto"; +import "frameworks/base/core/proto/android/stats/connectivity/tethering.proto"; import "frameworks/base/core/proto/android/stats/dnsresolver/dns_resolver.proto"; import "frameworks/base/core/proto/android/stats/devicepolicy/device_policy.proto"; import "frameworks/base/core/proto/android/stats/devicepolicy/device_policy_enums.proto"; @@ -144,8 +145,7 @@ message Atom { PacketWakeupOccurred packet_wakeup_occurred = 44 [(module) = "framework"]; WallClockTimeShifted wall_clock_time_shifted = 45 [(module) = "framework"]; AnomalyDetected anomaly_detected = 46 [(module) = "statsd"]; - AppBreadcrumbReported app_breadcrumb_reported = - 47 [(allow_from_any_uid) = true, (module) = "statsd"]; + AppBreadcrumbReported app_breadcrumb_reported = 47 [(module) = "statsd"]; AppStartOccurred app_start_occurred = 48 [(module) = "framework", (module) = "statsdtest"]; AppStartCanceled app_start_canceled = 49 [(module) = "framework"]; AppStartFullyDrawn app_start_fully_drawn = 50 [(module) = "framework"]; @@ -156,7 +156,7 @@ message Atom { AppStartMemoryStateCaptured app_start_memory_state_captured = 55 [(module) = "framework"]; ShutdownSequenceReported shutdown_sequence_reported = 56 [(module) = "framework"]; BootSequenceReported boot_sequence_reported = 57; - DaveyOccurred davey_occurred = 58 [(allow_from_any_uid) = true, (module) = "statsd"]; + DaveyOccurred davey_occurred = 58 [(module) = "statsd"]; OverlayStateChanged overlay_state_changed = 59 [(module) = "framework", (module) = "statsdtest"]; ForegroundServiceStateChanged foreground_service_state_changed @@ -185,15 +185,14 @@ message Atom { WTFOccurred wtf_occurred = 80 [(module) = "framework"]; LowMemReported low_mem_reported = 81 [(module) = "framework"]; GenericAtom generic_atom = 82; - KeyValuePairsAtom key_value_pairs_atom = - 83 [(allow_from_any_uid) = true, (module) = "framework", (module) = "statsd"]; + KeyValuePairsAtom key_value_pairs_atom = 83 [(module) = "framework", (module) = "statsd"]; VibratorStateChanged vibrator_state_changed = 84 [(module) = "framework"]; DeferredJobStatsReported deferred_job_stats_reported = 85 [(module) = "framework"]; ThermalThrottlingStateChanged thermal_throttling = 86 [deprecated=true]; BiometricAcquired biometric_acquired = 87 [(module) = "framework"]; BiometricAuthenticated biometric_authenticated = 88 [(module) = "framework"]; BiometricErrorOccurred biometric_error_occurred = 89 [(module) = "framework"]; - UiEventReported ui_event_reported = 90 [(module) = "framework"]; + UiEventReported ui_event_reported = 90 [(module) = "framework", (module) = "sysui"]; BatteryHealthSnapshot battery_health_snapshot = 91; SlowIo slow_io = 92; BatteryCausedShutdown battery_caused_shutdown = 93; @@ -316,7 +315,7 @@ message Atom { AssistGestureFeedbackReported assist_gesture_feedback_reported = 175 [(module) = "sysui"]; AssistGestureProgressReported assist_gesture_progress_reported = 176 [(module) = "sysui"]; TouchGestureClassified touch_gesture_classified = 177 [(module) = "framework"]; - HiddenApiUsed hidden_api_used = 178 [(allow_from_any_uid) = true, (module) = "framework"]; + HiddenApiUsed hidden_api_used = 178 [(module) = "framework"]; StyleUIChanged style_ui_changed = 179 [(module) = "sysui"]; PrivacyIndicatorsInteracted privacy_indicators_interacted = 180 [(module) = "permissioncontroller"]; @@ -382,7 +381,7 @@ message Atom { UpdateEngineSuccessfulUpdateReported update_engine_successful_update_reported = 226; CameraActionEvent camera_action_event = 227 [(module) = "framework"]; AppCompatibilityChangeReported app_compatibility_change_reported = - 228 [(allow_from_any_uid) = true, (module) = "framework"]; + 228 [(module) = "framework"]; PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"]; VmsClientConnectionStateChanged vms_client_connection_state_changed = 230 [(module) = "car"]; @@ -419,7 +418,7 @@ message Atom { DisplayJankReported display_jank_reported = 257; AppStandbyBucketChanged app_standby_bucket_changed = 258 [(module) = "framework"]; SharesheetStarted sharesheet_started = 259 [(module) = "framework"]; - RankingSelected ranking_selected = 260 [(module) = "framework"]; + RankingSelected ranking_selected = 260 [(module) = "framework", (module) = "sysui"]; TvSettingsUIInteracted tvsettings_ui_interacted = 261 [(module) = "tv_settings"]; LauncherStaticLayout launcher_snapshot = 262 [(module) = "sysui"]; PackageInstallerV2Reported package_installer_v2_reported = 263 [(module) = "framework"]; @@ -444,6 +443,48 @@ message Atom { TvTunerStateChanged tv_tuner_state_changed = 276 [(module) = "framework"]; MediaOutputOpSwitchReported mediaoutput_op_switch_reported = 277 [(module) = "settings"]; + CellBroadcastMessageFiltered cb_message_filtered = + 278 [(module) = "cellbroadcast"]; + TvTunerDvrStatus tv_tuner_dvr_status = 279 [(module) = "framework"]; + TvCasSessionOpenStatus tv_cas_session_open_status = + 280 [(module) = "framework"]; + AssistantInvocationReported assistant_invocation_reported = 281 [(module) = "framework"]; + DisplayWakeReported display_wake_reported = 282 [(module) = "framework"]; + CarUserHalModifyUserRequestReported car_user_hal_modify_user_request_reported = + 283 [(module) = "car"]; + CarUserHalModifyUserResponseReported car_user_hal_modify_user_response_reported = + 284 [(module) = "car"]; + CarUserHalPostSwitchResponseReported car_user_hal_post_switch_response_reported = + 285 [(module) = "car"]; + CarUserHalInitialUserInfoRequestReported car_user_hal_initial_user_info_request_reported = + 286 [(module) = "car"]; + CarUserHalInitialUserInfoResponseReported car_user_hal_initial_user_info_response_reported = + 287 [(module) = "car"]; + CarUserHalUserAssociationRequestReported car_user_hal_user_association_request_reported = + 288 [(module) = "car"]; + CarUserHalSetUserAssociationResponseReported car_user_hal_set_user_association_response_reported = + 289 [(module) = "car"]; + NetworkIpProvisioningReported network_ip_provisioning_reported = + 290 [(module) = "network_stack"]; + NetworkDhcpRenewReported network_dhcp_renew_reported = 291 [(module) = "network_stack"]; + NetworkValidationReported network_validation_reported = 292 [(module) = "network_stack"]; + NetworkStackQuirkReported network_stack_quirk_reported = 293 [(module) = "network_stack"]; + MediametricsAudioRecordDeviceUsageReported mediametrics_audiorecorddeviceusage_reported = + 294; + MediametricsAudioThreadDeviceUsageReported mediametrics_audiothreaddeviceusage_reported = + 295; + MediametricsAudioTrackDeviceUsageReported mediametrics_audiotrackdeviceusage_reported = + 296; + MediametricsAudioDeviceConnectionReported mediametrics_audiodeviceconnection_reported = + 297; + BlobCommitted blob_committed = 298 [(module) = "framework"]; + BlobLeased blob_leased = 299 [(module) = "framework"]; + BlobOpened blob_opened = 300 [(module) = "framework"]; + ContactsProviderStatusReported contacts_provider_status_reported = 301; + KeystoreKeyEventReported keystore_key_event_reported = 302; + NetworkTetheringReported network_tethering_reported = + 303 [(module) = "network_tethering"]; + ImeTouchReported ime_touch_reported = 304 [(module) = "sysui"]; // StatsdStats tracks platform atoms with ids upto 500. // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value. @@ -541,10 +582,13 @@ message Atom { SimSlotState sim_slot_state = 10078 [(module) = "telephony"]; SupportedRadioAccessFamily supported_radio_access_family = 10079 [(module) = "telephony"]; SettingSnapshot setting_snapshot = 10080 [(module) = "framework"]; - DisplayWakeReason display_wake_reason = 10081 [(module) = "framework"]; + BlobInfo blob_info = 10081 [(module) = "framework"]; DataUsageBytesTransfer data_usage_bytes_transfer = 10082 [(module) = "framework"]; BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered = 10083 [(module) = "framework"]; + DNDModeProto dnd_mode_rule = 10084 [(module) = "framework"]; + GeneralExternalStorageAccessStats general_external_storage_access_stats = + 10085 [(module) = "mediaprovider"]; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -3017,6 +3061,18 @@ message ExclusionRectStateChanged { optional int32 duration_millis = 7; } +/** + * Logs when IME is on. + * + * Logged from: /packages/SystemUI/src/com/android/systemui/ + statusbar/phone/NavigationBarView.java + * + */ +message ImeTouchReported { + optional int32 x_coordinate = 1; // X coordinate for ACTION_DOWN event. + optional int32 y_coordinate = 2; // Y coordinate for ACTION_DOWN event. +} + /** * Logs when Launcher (HomeScreen) UI has changed or was interacted. * @@ -4499,6 +4555,31 @@ message VmsClientConnectionStateChanged { optional State state = 2; } +message MimeTypes { + repeated string mime_types = 1; +} + +/** + * Logs statistics regarding accesses to external storage. + * All stats are normalized for one day period. + * + * Logged from: + * packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java + */ +message GeneralExternalStorageAccessStats { + optional int32 uid = 1 [(is_uid) = true]; + // Total number of accesses like creation, open, delete and rename/update. + // Includes file path and ContentResolver accesses + optional uint32 total_accesses = 2; + // Number of file path accesses, as opposed to file path and ContentResolver. + optional uint32 file_path_accesses = 3; + // Number of accesses on secondary volumes like SD cards. + // Includes file path and ContentResolver accesses + optional uint32 secondary_storage_accesses = 4; + // Comma-separated list of mime types that were accessed. + optional MimeTypes mime_types_accessed = 5; +} + /** * Logs when MediaProvider has successfully finished scanning a storage volume. * @@ -4870,6 +4951,98 @@ message SnapshotMergeReported { optional int64 cow_file_size_bytes = 5; } +/** + * Event representing when BlobStoreManager.Session#commit() is called + * + * Logged from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobCommitted { + // Uid of the Blob committer + optional int32 uid = 1 [(is_uid) = true]; + + // Id of the Blob committed + optional int64 blob_id = 2; + + // Size of the Blob + optional int64 size = 3; + + enum Result { + UNKNOWN = 0; + // Commit Succeeded + SUCCESS = 1; + // Commit Failed: Error occurred during commit + ERROR_DURING_COMMIT = 2; + // Commit Failed: Digest of the data did not match Blob digest + DIGEST_MISMATCH = 3; + // Commit Failed: Allowed count limit exceeded + COUNT_LIMIT_EXCEEDED = 4; + } + optional Result result = 4; +} + +/** + * Event representing when BlobStoreManager#acquireLease() is called + * + * Logged from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobLeased{ + // Uid of the Blob leasee + optional int32 uid = 1 [(is_uid) = true]; + + // Id of the Blob leased or 0 if the Blob does not exist + optional int64 blob_id = 2; + + // Size of the Blob or 0 if the Blob does not exist + optional int64 size = 3; + + enum Result { + UNKNOWN = 0; + // Lease Succeeded + SUCCESS = 1; + // Lease Failed: Blob does not exist + BLOB_DNE = 2; + // Lease Failed: Leasee does not have access to the Blob + ACCESS_NOT_ALLOWED = 3; + // Lease Failed: Leasee requested an invalid expiry duration + LEASE_EXPIRY_INVALID = 4; + // Lease Failed: Leasee has exceeded the total data lease limit + DATA_SIZE_LIMIT_EXCEEDED = 5; + // Leasee Failed: Allowed count limit exceeded + COUNT_LIMIT_EXCEEDED = 6; + } + optional Result result = 4; +} + +/** + * Event representing when BlobStoreManager#openBlob() is called + * + * Logged from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobOpened{ + // Uid of the Blob opener + optional int32 uid = 1 [(is_uid) = true]; + + // Id of the Blob opened or 0 if the Blob does not exist + optional int64 blob_id = 2; + + // Size of the Blob or 0 if the Blob does not exist + optional int64 size = 3; + + enum Result { + UNKNOWN = 0; + // Open Succeeded + SUCCESS = 1; + // Open Failed: Blob does not exist + BLOB_DNE = 2; + // Open Failed: Opener does not have access to the Blob + ACCESS_NOT_ALLOWED = 3; + } + optional Result result = 4; +} + ////////////////////////////////////////////////////////////////////// // Pulled atoms below this line // ////////////////////////////////////////////////////////////////////// @@ -5798,7 +5971,7 @@ message ProcessStatsProto { optional string process = 1; // Uid of the process. - optional int32 uid = 2; + optional int32 uid = 2 [(is_uid) = true]; // Information about how often kills occurred message Kill { @@ -5825,13 +5998,16 @@ message ProcessStatsProto { repeated ProcessStatsAssociationProto assocs = 7; } -// Next Tag: 5 +// Next Tag: 6 message ProcessStatsAssociationProto { // Procss Name of the associated process (client process of service binding) optional string assoc_process_name = 1; // Package Name of the associated package (client package of service binding) - optional string assoc_package_name = 2; + optional string assoc_package_name = 2 [deprecated = true]; + + // UID of the associated process/package (client package of service binding) + optional int32 assoc_uid = 5 [(is_uid) = true]; // Total count of the times this association (service binding) appeared. optional int32 total_count = 3; @@ -5986,6 +6162,10 @@ message ProcessStatsAvailablePagesProto { */ message ProcStats { optional ProcessStatsSectionProto proc_stats_section = 1; + // Data pulled from device into this is sometimes sharded across multiple atoms to work around + // a size limit. When this happens, this shard ID will contain an increasing 1-indexed integer + // with the number of this shard. + optional int32 shard_id = 2; } /** @@ -6058,6 +6238,76 @@ message PackageNotificationChannelPreferences { optional bool is_important_conversation = 10; } +/** + * Atom that represents an item in the list of Do Not Disturb rules, pulled from + * NotificationManagerService.java. + */ +message DNDModeProto { + enum Mode { + ROOT_CONFIG = -1; // Used to distinguish the config (one per user) from the rules. + ZEN_MODE_OFF = 0; + ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; + ZEN_MODE_NO_INTERRUPTIONS = 2; + ZEN_MODE_ALARMS = 3; + } + optional int32 user = 1; // Android user ID (0, 1, 10, ...) + optional bool enabled = 2; // true for ROOT_CONFIG if a manualRule is enabled + optional bool channels_bypassing = 3; // only valid for ROOT_CONFIG + optional Mode zen_mode = 4; + // id is one of the system default rule IDs, or empty + // May also be "MANUAL_RULE" to indicate app-activation of the manual rule. + optional string id = 5; + optional int32 uid = 6 [(is_uid) = true]; // currently only SYSTEM_UID or 0 for other + optional DNDPolicyProto policy = 7; +} + +/** + * Atom that represents a Do Not Disturb policy, an optional detail proto for DNDModeProto. + */ +message DNDPolicyProto { + enum State { + STATE_UNSET = 0; + STATE_ALLOW = 1; + STATE_DISALLOW = 2; + } + optional State calls = 1; + optional State repeat_callers = 2; + optional State messages = 3; + optional State conversations = 4; + optional State reminders = 5; + optional State events = 6; + optional State alarms = 7; + optional State media = 8; + optional State system = 9; + optional State fullscreen = 10; + optional State lights = 11; + optional State peek = 12; + optional State status_bar = 13; + optional State badge = 14; + optional State ambient = 15; + optional State notification_list = 16; + + enum PeopleType { + PEOPLE_UNSET = 0; + PEOPLE_ANYONE = 1; + PEOPLE_CONTACTS = 2; + PEOPLE_STARRED = 3; + PEOPLE_NONE = 4; + } + + optional PeopleType allow_calls_from = 17; + optional PeopleType allow_messages_from = 18; + + enum ConversationType { + CONV_UNSET = 0; + CONV_ANYONE = 1; + CONV_IMPORTANT = 2; + CONV_NONE = 3; + } + + optional ConversationType allow_conversations_from = 19; +} + /** * Atom that contains a list of a package's channel group preferences, pulled from * NotificationManagerService.java. @@ -6309,6 +6559,16 @@ message ContentCaptureServiceEvents { SET_WHITELIST = 3; SET_DISABLED = 4; ON_USER_DATA_REMOVED = 5; + ON_DATA_SHARE_REQUEST = 6; + ACCEPT_DATA_SHARE_REQUEST = 7; + REJECT_DATA_SHARE_REQUEST = 8; + DATA_SHARE_WRITE_FINISHED = 9; + DATA_SHARE_ERROR_IOEXCEPTION = 10; + DATA_SHARE_ERROR_EMPTY_DATA = 11; + DATA_SHARE_ERROR_CLIENT_PIPE_FAIL = 12; + DATA_SHARE_ERROR_SERVICE_PIPE_FAIL = 13; + DATA_SHARE_ERROR_CONCURRENT_REQUEST = 14; + DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 15; } optional Event event = 1; // component/package of content capture service. @@ -6679,6 +6939,24 @@ message AppCompacted { optional int64 after_zram_free_kilobytes = 18; } +/** + * Logs when a Tethering event occurs. + * + */ +message NetworkTetheringReported { + // tethering error code + optional android.stats.connectivity.ErrorCode error_code = 1; + + // tethering downstream type + optional android.stats.connectivity.DownstreamType downstream_type = 2; + + // transport type of upstream network + optional android.stats.connectivity.UpstreamType upstream_type = 3; + + // The user type of Tethering + optional android.stats.connectivity.UserType user_type= 4; +} + /** * Logs a DNS lookup operation initiated by the system resolver on behalf of an application * invoking native APIs such as getaddrinfo() or Java APIs such as Network#getAllByName(). @@ -6716,6 +6994,172 @@ message NetworkDnsEventReported { optional int32 sampling_rate_denom = 9; } +/** + * logs the CapportApiData info + * Logged from: + * packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java + */ +message CapportApiData { + // The TTL of the network connection provided by captive portal + optional int32 remaining_ttl_secs = 1; + + // The limit traffic data of the network connection provided by captive portal + optional int32 remaining_bytes = 2; + + // Is portal url option included in the DHCP packet (Yes, No) + optional bool has_portal_url = 3; + + // Is venue info (e.g. store info, maps, flight status) included (Yes, No) + optional bool has_venue_info = 4; +} + +/** + * logs a network Probe Event + * Logged from: + * packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java + */ +message ProbeEvent { + // The probe type (http or https, or captive portal API...) + optional android.stats.connectivity.ProbeType probe_type = 1; + + // The latency in microseconds of the probe event + optional int32 latency_micros = 2; + + // The result of the probe event + optional android.stats.connectivity.ProbeResult probe_result = 3; + + // The CaptivePortal API info + optional CapportApiData capport_api_data = 4; +} + +/** + * log each ProbeEvent in ProbeEvents + * Logged from: + * packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java + */ +message ProbeEvents { + // Record probe event during the validation + repeated ProbeEvent probe_event = 1; +} + +/** + * The DHCP (Dynamic Host Configuration Protocol) session info + * Logged from: + * packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java + */ +message DhcpSession { + // The DHCP Feature(s) enabled in this session + repeated android.stats.connectivity.DhcpFeature used_features = 1; + + // The discover packet (re)transmit count + optional int32 discover_count = 2; + + // The request packet (re)transmit count + optional int32 request_count = 3; + + // The IPv4 address conflict count + // (only be meaningful when duplicate address detection is enabled) + optional int32 conflict_count = 4; + + // The DHCP packet parsing error code in this session + // (defined in android.net.metrics.DhcpErrorEvent) + repeated android.stats.connectivity.DhcpErrorCode error_code = 5; + + // The result of DHCP hostname transliteration + optional android.stats.connectivity.HostnameTransResult ht_result = 6; +} + +/** + * Logs Network IP provisioning event + * Logged from: + * packages/modules/NetworkStack/src/com/android/networkstack/metrics/NetworkIpProvisioningMetrics.java + */ +message NetworkIpProvisioningReported { + // Transport type (WIFI, CELLULAR, BLUETOOTH, ..) + optional android.stats.connectivity.TransportType transport_type = 1; + + // The latency in microseconds of IP Provisioning over IPV4 + optional int32 ipv4_latency_micros = 2; + + // The latency in microseconds of IP Provisioning over IPV6 + optional int32 ipv6_latency_micros = 3; + + // The time duration between provisioning start and end (success or failure) + optional int64 provisioning_duration_micros = 4; + + // The specific disconnect reason for this IP provisioning + optional android.stats.connectivity.DisconnectCode disconnect_code = 5; + + // Log DHCP session info (Only valid for IPv4) + optional DhcpSession dhcp_session = 6 [(log_mode) = MODE_BYTES]; + + // The random number between 0 ~ 999 for sampling + optional int32 random_number = 7; +} + +/** + * Logs Network DHCP Renew event + * Logged from: + * packages/modules/NetworkStack/src/android/net/dhcp/DhcpClient.java + */ +message NetworkDhcpRenewReported { + // Transport type (WIFI, CELLULAR, BLUETOOTH, ..) + optional android.stats.connectivity.TransportType transport_type = 1; + + // The request packet (re)transmit count + optional int32 request_count = 2; + + // The latency in microseconds of DHCP Renew + optional int32 latency_micros = 3; + + // The DHCP error code is defined in android.net.metrics.DhcpErrorEvent + optional android.stats.connectivity.DhcpErrorCode error_code = 4; + + // The result of DHCP renew + optional android.stats.connectivity.DhcpRenewResult renew_result = 5; + + // The random number between 0 ~ 999 for sampling + optional int32 random_number = 6; +} + +/** + * Logs Network Validation event + * Logged from: + * packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java + */ +message NetworkValidationReported { + // Transport type (WIFI, CELLULAR, BLUETOOTH, ..) + optional android.stats.connectivity.TransportType transport_type = 1; + + // Record each probe event + optional ProbeEvents probe_events = 2 [(log_mode) = MODE_BYTES]; + + // The result of the network validation + optional android.stats.connectivity.ValidationResult validation_result = 3; + + // The latency in microseconds of network validation + optional int32 latency_micros = 4; + + // The validation index (the first validation attempt or second, third...) + optional int32 validation_index = 5; + + // The random number between 0 ~ 999 for sampling + optional int32 random_number = 6; +} + +/** + * Logs NetworkStack Quirk event + * Logged from: + * packages/modules/NetworkStack/src/com/android/networkstack/ + */ +message NetworkStackQuirkReported { + // Transport type (WIFI, CELLULAR, BLUETOOTH, ..) + optional android.stats.connectivity.TransportType transport_type = 1; + + // Record each Quirk event + optional android.stats.connectivity.NetworkQuirkEvent event = 2; +} + /** * Logs when a data stall event occurs. * @@ -7991,6 +8435,245 @@ message CarPowerStateChanged { optional State state = 1; } +/** + * Logs when Car User Hal is requested to switch/create/remove user. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalModifyUserRequestReported { + // Request id for the request. + optional int32 request_id = 1; + // Request type. + enum RequestType { + UNKNOWN = 0; + // Car user manager requested user switch. + SWITCH_REQUEST_ANDROID = 1; + // OEM requested User switch. + SWITCH_REQUEST_OEM = 2; + // Hal switch requested after android switch using activity manager. + SWITCH_REQUEST_LEGACY = 3; + // Create User + CREATE_REQUEST = 4; + // Remove User + REMOVE_REQUEST = 5; + } + optional RequestType request_type = 2; + // Android User id of the current user which can only be 0, 10, 11 and so on. + // -1 if not available. + optional int32 user_id = 3; + // VHAL flags of the current user. (-1 if not available) + optional int32 user_flags = 4; + // Android User id of the target user for switch/create/remove. It can only + // be 0, 10, 11 and so on. -1 if not available. + optional int32 target_user_id = 5; + // VHAL flags of the target user for switch/create/remove. (-1 if not available) + optional int32 target_user_flags = 6; + // Request timeout Milliseconds (-1 if not available) + optional int32 timeout_millis = 7; +} + +/** + * Logs when Car User Hal responds to switch/create user request. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalModifyUserResponseReported { + // Request id of the request associated with the response. + optional int32 request_id = 1; + // Car user hal callback status. + enum CallbackStatus { + UNKNOWN = 0; + // Hal response was invalid. + INVALID = 1; + // Hal response was ok. + OK = 2; + // Hal timeout during set call. + HAL_SET_TIMEOUT = 3; + // Hal response timeout. + HAL_RESPONSE_TIMEOUT = 4; + // Hal responded with wrong info. + WRONG_HAL_RESPONSE = 5; + // Hal is processing multiple requests simultaneously. + CONCURRENT_OPERATION = 6; + } + optional CallbackStatus callback_status = 2; + + // Hal request status for user switch/create/remove. + enum HalRequestStatus { + UNSPECIFIED = 0; + // Hal request for user switch/create is successful. + SUCCESS = 1; + // Hal request for user switch/create failed. + FAILURE = 2; + } + optional HalRequestStatus request_status = 3; +} + +/** + * Logs when post switch response is posted to Car User Hal. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalPostSwitchResponseReported { + // Request id. + optional int32 request_id = 1; + + // Android user switch status. + enum UserSwitchStatus { + UNKNOWN = 0; + // Android user switch is successful. + SUCCESS = 1; + // Android user switch failed. + FAILURE = 2; + } + optional UserSwitchStatus switch_status = 2; +} + +/** + * Logs when initial user information is requested from Car User Hal. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalInitialUserInfoRequestReported { + // Request id for the request. + optional int32 request_id = 1; + + // Request type for initial user information. + enum InitialUserInfoRequestType { + UNKNOWN = 0; + // At the first time Android was booted (or after a factory reset). + FIRST_BOOT = 1; + // At the first time Android was booted after the system was updated. + FIRST_BOOT_AFTER_OTA = 2; + // When Android was booted "from scratch". + COLD_BOOT = 3; + // When Android was resumed after the system was suspended to memory. + RESUME = 4; + } + optional InitialUserInfoRequestType request_type = 2; + // Request timeout Milliseconds (-1 if not available) + optional int32 timeout_millis = 3; +} + +/** + * Logs when Car User Hal responds to initial user information requests. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalInitialUserInfoResponseReported { + // Request id of the request associated with the response. + optional int32 request_id = 1; + // Car user hal callback status. + enum CallbackStatus { + UNKNOWN = 0; + // Hal response was invalid. + INVALID = 1; + // Hal response was ok. + OK = 2; + // Hal timeout during set call. + HAL_SET_TIMEOUT = 3; + // Hal response timeout. + HAL_RESPONSE_TIMEOUT = 4; + // Hal responded with wrong info. + WRONG_HAL_RESPONSE = 5; + // Hal is processing multiple requests simultaneously. + CONCURRENT_OPERATION = 6; + } + optional CallbackStatus callback_status = 2; + // Response for initial user information request. + enum InitialUserInfoResponseAction { + UNSPECIFIED = 0; + // Let the Android System decide what to do. + DEFAULT = 1; + // Switch to an existing Android user. + SWITCH = 2; + // Create a new Android user (and switch to it). + CREATE = 3; + } + optional InitialUserInfoResponseAction response_action = 3; + // Android User id of the target user which can only be 0, 10, 11 and so on. + // -1 if not available. + optional int32 target_user = 4; + // VHAL flags of the current user. (-1 if not available) + optional int32 target_user_flags = 5; + // User locales + optional string user_locales = 6; +} + +/** + * Logs when set user association is requested from Car User Hal. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalUserAssociationRequestReported { + // Request id for the request. + optional int32 request_id = 1; + // Request type. + enum RequestType { + UNKNOWN = 0; + // For setting user association information. + SET = 1; + // For getting user association information. + GET = 2; + } + optional RequestType request_type = 2; + // Android User id of the current user which can only be 0, 10, 11 and so on. + // -1 if not available. + optional int32 current_user_id = 3; + // VHAL flags of the current user. (-1 if not available) + optional int32 current_user_flags = 4; + // Number of the set associations requested. + optional int32 number_associations = 5; + // Concatenated string for the types from set associations request. + // This is a string converted from an array of integers. + optional string user_identification_association_types = 6; + // Concatenated string for the values from set associations request. + // This is a string converted from an array of integers. + optional string user_identification_association_values = 7; +} + +/** + * Logs when Car User Hal responds to set user association requests. + * + * Logged from: + * packages/services/Car/service/src/com/android/car/hal/UserHalService.java + */ +message CarUserHalSetUserAssociationResponseReported { + // Request id of the request associated with the response. + optional int32 request_id = 1; + // Car user hal callback status. + enum CallbackStatus { + UNKNOWN = 0; + // Hal response was invalid. + INVALID = 1; + // Hal response was ok. + OK = 2; + // Hal timeout during set call. + HAL_SET_TIMEOUT = 3; + // Hal response timeout. + HAL_RESPONSE_TIMEOUT = 4; + // Hal responded with wrong info. + WRONG_HAL_RESPONSE = 5; + // Hal is processing multiple requests simultaneously. + CONCURRENT_OPERATION = 6; + } + optional CallbackStatus callback_status = 2; + // Number of the set associations in the response. + optional int32 number_associations = 3; + // Concatenated string for the types from set associations request. + // This is a string converted from an array of integers. + optional string user_identification_association_types = 4; + // Concatenated string for the values from set associations request. + // This is a string converted from an array of integers. + optional string user_identification_association_values = 5; +} + /** * Logs whether GarageMode is entered. * @@ -9042,6 +9725,7 @@ message RuntimeAppOpAccess { UNIFORM = 1; RARELY_USED = 2; BOOT_TIME_SAMPLING = 3; + UNIFORM_OPS = 4; } // sampling strategy used to collect this message @@ -9134,8 +9818,10 @@ message IntegrityRulesPushed { /** * Logs when a cell broadcast message is received on the device. * - * Logged from CellBroadcastService module: + * Logged from Cell Broadcast module and platform: * packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/ + * packages/apps/CellBroadcastReceiver/ + * frameworks/opt/telephony/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java */ message CellBroadcastMessageReported { // The type of Cell Broadcast message @@ -9146,8 +9832,40 @@ message CellBroadcastMessageReported { CDMA_SPC = 3; } + // The parts of the cell broadcast message pipeline + enum ReportSource { + UNKNOWN_SOURCE = 0; + FRAMEWORK = 1; + CB_SERVICE = 2; + CB_RECEIVER_APP = 3; + } + // GSM, CDMA, CDMA-SCP optional CbType type = 1; + + // The source of the report + optional ReportSource source = 2; +} + +/** + * Logs when a cell broadcast message is filtered out, or otherwise intentionally not sent to CBR. + * + * Logged from CellBroadcastService module: + * packages/modules/CellBroadcastService/src/com/android/cellbroadcastservice/ + */ +message CellBroadcastMessageFiltered { + enum FilterReason { + NOT_FILTERED = 0; + DUPLICATE_MESSAGE = 1; + GEOFENCED_MESSAGE = 2; + AREA_INFO_MESSAGE = 3; + } + + // GSM, CDMA, CDMA-SCP + optional CellBroadcastMessageReported.CbType type = 1; + + // The source of the report + optional FilterReason filter = 2; } /** @@ -9174,6 +9892,7 @@ message CellBroadcastMessageError { UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK = 12; UNEXPECTED_CDMA_MESSAGE_TYPE_FROM_FWK = 13; UNEXPECTED_CDMA_SCP_MESSAGE_TYPE_FROM_FWK = 14; + NO_CONNECTION_TO_CB_SERVICE = 15; } // What kind of error occurred @@ -9205,6 +9924,100 @@ message TvTunerStateChanged { // new state optional State state = 2; } + +/** + * Logs the status of a dvr playback or record. + * This is atom ID 279. + * + * Logged from: + * frameworks/base/media/java/android/media/tv/tuner/dvr + */ +message TvTunerDvrStatus { + enum Type { + UNKNOWN_TYPE = 0; + PLAYBACK = 1; // is a playback + RECORD = 2; // is a record + } + enum State { + UNKNOWN_STATE = 0; + STARTED = 1; // DVR is started + STOPPED = 2; // DVR is stopped + } + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + // DVR type + optional Type type = 2; + // DVR state + optional State state = 3; + // Identify the segment of a record or playback + optional int32 segment_id = 4; + // indicate how many overflow or underflow happened between started to stopped + optional int32 overflow_underflow_count = 5; +} + +/** + * Logs when a cas session opened through MediaCas. + * This is atom ID 280. + * + * Logged from: + * frameworks/base/media/java/android/media/MediaCas.java + */ +message TvCasSessionOpenStatus { + enum State { + UNKNOWN = 0; + SUCCEEDED = 1; // indicate that the session is opened successfully. + FAILED = 2; // indicate that the session isn’t opened successfully. + } + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + // Cas system Id + optional int32 cas_system_id = 2; + // State of the session + optional State state = 3; +} + +/** + * Logs for ContactsProvider general usage. + * This is atom ID 301. + * + * Logged from: + * packages/providers/ContactsProvider/src/com/android/providers/contacts/ContactsProvider2.java + */ +message ContactsProviderStatusReported { + enum ApiType { + UNKNOWN_API = 0; + QUERY = 1; + // INSERT includes insert and bulkInsert, and inserts triggered by applyBatch. + INSERT = 2; + // UPDATE and DELETE includes update/delete and the ones triggered by applyBatch. + UPDATE = 3; + DELETE = 4; + } + + enum ResultType { + UNKNOWN_RESULT = 0; + SUCCESS = 1; + FAIL = 2; + ILLEGAL_ARGUMENT = 3; + UNSUPPORTED_OPERATION = 4; + } + + enum CallerType { + UNSPECIFIED_CALLER_TYPE = 0; + CALLER_IS_SYNC_ADAPTER = 1; + CALLER_IS_NOT_SYNC_ADAPTER = 2; + } + + optional ApiType api_type = 1; + // Defined in + // packages/providers/ContactsProvider/src/com/android/providers/contacts/ContactsProvider2.java + optional int32 uri_type = 2; + optional CallerType caller_type = 3; + optional ResultType result_type = 4; + optional int32 result_count = 5; + optional int64 latency_micros = 6; +} + /** * Logs when an app is frozen or unfrozen. * @@ -9398,7 +10211,7 @@ message GnssStats { optional int64 time_to_first_fix_reports = 3; // Total pulled reported time to first fix (in milli-seconds) since boot - optional int64 time_to_first_fix_milli_s = 4; + optional int64 time_to_first_fix_millis = 4; // Number of position accuracy reports since boot optional int64 position_accuracy_reports = 5; @@ -9721,15 +10534,20 @@ message AccessibilityServiceReported { optional android.stats.accessibility.ServiceStatus service_status = 2; } -message DisplayWakeReason { +/** + * Logs when display wake up. + * + * Logged from: + * services/core/java/com/android/server/power/Notifier.java + */ + +message DisplayWakeReported { // Wake_up_reason code // If LOWORD(wake_up_reason) = 0 // reference to HIWORD(wake_up_reason) PowerManager.WAKE_REASON_XXX // else reference wake_up_reason to - // frameworks/base/services/core/java/com/android/server/power/Notifier.java#DispWakeupReason + // services/core/java/com/android/server/power/Notifier.java#onWakeUp optional int32 wake_up_reason = 1; - // Count of wake up by reason - optional int32 wake_times = 2; } /** @@ -9903,3 +10721,453 @@ message MediaOutputOpSwitchReported { // The amount of applied devices within a remote dynamic group after a switching is done. optional int32 applied_device_count_within_remote_group = 9; } + +/** + * Logs when the Assistant is invoked. + * + * Logged from: + * frameworks/base/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java + */ +message AssistantInvocationReported { + + // The event_id (as for UiEventReported). + optional int32 event_id = 1; + + // The registered Assistant's uid and package (as for UiEventReported). + optional int32 uid = 2 [(is_uid) = true]; + optional string package_name = 3; + + // An identifier used to disambiguate which logs refer to a particular invocation of the + // Assistant (as for UiEventReported). + optional int32 instance_id = 4; + + // The state of the device at the time of invocation. + enum DeviceState { + UNKNOWN_DEVICE_STATE = 0; + AOD1 = 1; + AOD2 = 2; + BOUNCER = 3; + UNLOCKED_LOCKSCREEN = 4; + LAUNCHER_HOME = 5; + LAUNCHER_OVERVIEW = 6; + LAUNCHER_ALL_APPS = 7; + APP_DEFAULT = 8; + APP_IMMERSIVE = 9; + APP_FULLSCREEN = 10; + } + optional DeviceState device_state = 5; + + // Whether the Assistant handles were showing at the time of invocation. + optional bool assistant_handles_showing = 6; +} + +/** + * Logs when an AudioRecord finishes running on an audio device + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioRecordDeviceUsageReported { + // The devices connected to this AudioRecord. + // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2". + // See lookup() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string devices = 1; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 2; + + // The amount of time spent in the device as measured by the active track in AudioFlinger. + optional int64 device_time_nanos = 3; + + // The audio data format used for encoding. + // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t + optional string encoding = 4; + + // The client-server buffer framecount. + // The framecount is generally between 960 - 48000 for PCM encoding. + // The framecount represents raw buffer size in bytes for non-PCM encoding. + optional int32 frame_count = 5; + + // The number of audio intervals (contiguous, continuous playbacks). + optional int32 interval_count = 6; + + // The sample rate of the AudioRecord. + // A number generally between 8000-96000 (frames per second). + optional int32 sample_rate = 7; + + // The audio input flags used to construct the AudioRecord. + // A string OR from system/media/audio/include/system/audio-base.h audio_input_flags_t + optional string flags = 8; + + // The santized package name of the audio client associated with the AudioRecord. + // See getSanitizedPackageNameAndVersionCode() in + // frameworks/av/services/mediametrics/MediaMetricsService.cpp + optional string package_name = 9; + + // The selected device id (nonzero if a non-default device is selected) + optional int32 selected_device_id = 10; + + // The caller of the AudioRecord. + // See lookup() in frameworks/av/services/mediametrics/AudioTypes.cpp + optional string caller = 11; + + // The audio source for AudioRecord. + // An enumeration from system/media/audio/include/system/audio-base.h audio_source_t + optional string source = 12; +} + +/** + * Logs when an AudioThread finishes running on an audio device + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioThreadDeviceUsageReported { + // The devices connected to this audio thread. + // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2". + // (for record threads): + // See lookup in frameworks/av/services/mediametrics/AudioTypes.cpp + // (for playback threads): + // See lookup() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string devices = 1; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 2; + + // The amount of time spent in the device as measured by the active track in AudioFlinger. + optional int64 device_time_nanos = 3; + + // The audio data format used for encoding. + // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t + optional string encoding = 4; + + // The framecount of the buffer delivered to (or from) the HAL. + // The framecount is generally ~960 for PCM encoding. + // The framecount represents raw buffer size in bytes for non-PCM encoding. + optional int32 frame_count = 5; + + // The number of audio intervals (contiguous, continuous playbacks). + optional int32 interval_count = 6; + + // The sample rate of the audio thread. + // A number generally between 8000-96000 (frames per second). + optional int32 sample_rate = 7; + + // The audio flags used to construct the thread + // (for record threads): + // A string OR from system/media/audio/include/system/audio-base.h audio_input_flags_t + // (for playback threads): + // A string OR from system/media/audio/include/system/audio-base.h audio_output_flags_t + optional string flags = 8; + + // The number of underruns encountered for a playback thread or the + // number of overruns encountered for a capture thread. + optional int32 xruns = 9; + + // The type of thread + // A thread type enumeration from + // frameworks/av/mediametrics/services/Translate.h + optional string type = 10; +} + +/** + * Logs when an AudioTrack finishes running on an audio device + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioTrackDeviceUsageReported { + // The output devices connected to this AudioTrack. + // A string OR of various output device categories, e.g. "DEVICE1|DEVICE2". + // See lookup() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string devices = 1; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 2; + + // The amount of time spent in the device as measured by the active track in AudioFlinger. + optional int64 device_time_nanos = 3; + + // The audio data format used for encoding. + // An enumeration from system/media/audio/include/system/audio-base.h audio_format_t + optional string encoding = 4; + + // The client-server buffer framecount. + // The framecount is generally between 960 - 48000 for PCM encoding. + // The framecount represents raw buffer size in bytes for non-PCM encoding. + // A static track (see traits) may have a very large framecount. + optional int32 frame_count = 5; + + // The number of audio intervals (contiguous, continuous playbacks). + optional int32 interval_count = 6; + + // The sample rate of the AudioTrack. + // A number generally between 8000-96000 (frames per second). + optional int32 sample_rate = 7; + + // The audio flags used to construct the AudioTrack. + // A string OR from system/media/audio/include/system/audio-base.h audio_output_flags_t + optional string flags = 8; + + // The number of underruns encountered. + optional int32 xruns = 9; + + // The santized package name of the audio client associated with the AudioTrack. + // See getSanitizedPackageNameAndVersionCode() in + // frameworks/av/services/mediametrics/MediaMetricsService.cpp + optional string package_name = 10; + + // The latency of the last sample in the buffer in milliseconds. + optional float device_latency_millis = 11; + + // The startup time in milliseconds from start() to sample played. + optional float device_startup_millis = 12; + + // The average volume of the track on the device [ 0.f - 1.f ] + optional float device_volume = 13; + + // The selected device id (nonzero if a non-default device is selected) + optional int32 selected_device_id = 14; + + // The stream_type category for the AudioTrack. + // An enumeration from system/media/audio/include/system/audio-base.h audio_stream_type_t + optional string stream_type = 15; + + // The usage for the AudioTrack. + // An enumeration from system/media/audio/include/system/audio-base.h audio_usage_t + optional string usage = 16; + + // The content type of the AudioTrack. + // An enumeration from system/media/audio/include/system/audio-base.h audio_content_type_t + optional string content_type = 17; + + // The caller of the AudioTrack. + // See lookup() in frameworks/av/services/mediametrics/AudioTypes.cpp + optional string caller = 18; + + // The traits of the AudioTrack. + // A string OR of different traits, may be empty string. + // Only "static" is supported for R. + // See lookup() in frameworks/av/services/mediametrics/AudioTypes.cpp + optional string traits = 19; +} + +/** + * Logs the status of an audio device connection attempt. + * + * Logged from: + * frameworks/av/services/mediametrics/AudioAnalytics.cpp + */ +message MediametricsAudioDeviceConnectionReported { + // The input devices represented by this report. + // A string OR of various input device categories, e.g. "DEVICE1|DEVICE2". + // See lookup() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string input_devices = 1; + + // The output devices represented by this report. + // A string OR of various output device categories. + // See lookup() in frameworks/av/services/mediametrics/AudioTypes.cpp + // See audio_device_t in system/media/audio/include/system/audio-base.h + optional string output_devices = 2; + + // The name of the remote device attached to the device, typically available for USB or BT. + // This may be empty for a fixed device, or separated by "|" if more than one. + optional string device_names = 3; + + // The result of the audio device connection. + // 0 indicates success: connection verified. + // 1 indicates unknown: connection not verified or not known if diverted properly. + // Other values indicate specific status. + // See DeviceConnectionResult in frameworks/av/services/mediametrics/AudioTypes.h + optional int32 result = 4; + + // Average milliseconds of time to connect + optional float time_to_connect_millis = 5; + + // Number of connections if aggregated statistics, otherwise 1. + optional int32 connection_count = 6; +} + +/** + * Logs: i) creation of different types of cryptographic keys in the keystore, + * ii) operations performed using the keys, + * iii) attestation of the keys + * Logged from: system/security/keystore/key_event_log_handler.cpp + */ +message KeystoreKeyEventReported { + + enum Algorithm { + /** Asymmetric algorithms. */ + RSA = 1; + // 2 removed, do not reuse. + EC = 3; + /** Block cipher algorithms */ + AES = 32; + TRIPLE_DES = 33; + /** MAC algorithms */ + HMAC = 128; + }; + /** Algorithm associated with the key */ + optional Algorithm algorithm = 1; + + /** Size of the key */ + optional int32 key_size = 2; + + enum KeyOrigin { + /** Generated in keymaster. Should not exist outside the TEE. */ + GENERATED = 0; + /** Derived inside keymaster. Likely exists off-device. */ + DERIVED = 1; + /** Imported into keymaster. Existed as cleartext in Android. */ + IMPORTED = 2; + /** Keymaster did not record origin. */ + UNKNOWN = 3; + /** Securely imported into Keymaster. */ + SECURELY_IMPORTED = 4; + }; + /* Logs whether the key was generated, imported, securely imported, or derived.*/ + optional KeyOrigin key_origin = 3; + + enum HardwareAuthenticatorType { + NONE = 0; + PASSWORD = 1; + FINGERPRINT = 2; + // Additional entries must be powers of 2. + }; + /** + * What auth types does this key require? If none, + * then no auth required. + */ + optional HardwareAuthenticatorType user_auth_type = 4; + + /** + * If user authentication is required, is the requirement time based? If it + * is not time based then this field will not be used and the key is per + * operation. Per operation keys must be user authenticated on each usage. + */ + optional int32 user_auth_key_timeout_secs = 5; + + /** + * padding mode, digest, block_mode and purpose should ideally be repeated + * fields. However, since statsd does not support repeated fields in + * pushed atoms, they are represented using bitmaps. + */ + + /** Track which padding mode is being used.*/ + optional int32 padding_mode_bitmap = 6; + + /** Track which digest is being used. */ + optional int32 digest_bitmap = 7; + + /** Track what block mode is being used (for encryption). */ + optional int32 block_mode_bitmap = 8; + + /** Track what purpose is this key serving. */ + optional int32 purpose_bitmap = 9; + + enum EcCurve { + P_224 = 0; + P_256 = 1; + P_384 = 2; + P_521 = 3; + }; + /** Which ec curve was selected if elliptic curve cryptography is in use **/ + optional EcCurve ec_curve = 10; + + enum KeyBlobUsageRequirements { + STANDALONE = 0; + REQUIRES_FILE_SYSTEM = 1; + }; + /** Standalone or is a file system required */ + optional KeyBlobUsageRequirements key_blob_usage_reqs = 11; + + enum Type { + key_operation = 0; + key_creation = 1; + key_attestation = 2; + } + /** Key creation event, operation event or attestation event? */ + optional Type type = 12; + + /** Was the key creation, operation, or attestation successful? */ + optional bool was_successful = 13; + + /** Response code or error code */ + optional int32 error_code = 14; +} + +// Blob Committer stats +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobCommitterProto { + // Committer app's uid + optional int32 uid = 1 [(is_uid) = true]; + + // Unix epoch timestamp of the commit in milliseconds + optional int64 commit_timestamp_millis = 2; + + // Flags of what access types the committer has set for the Blob + optional int32 access_mode = 3; + + // Number of packages that have been whitelisted for ACCESS_TYPE_WHITELIST + optional int32 num_whitelisted_package = 4; +} + +// Blob Leasee stats +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobLeaseeProto { + // Leasee app's uid + optional int32 uid = 1 [(is_uid) = true]; + + // Unix epoch timestamp for lease expiration in milliseconds + optional int64 lease_expiry_timestamp_millis = 2; +} + +// List of Blob Committers +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobCommitterListProto { + repeated BlobCommitterProto committer = 1; +} + +// List of Blob Leasees +// Keep in sync between: +// frameworks/base/core/proto/android/server/blobstoremanagerservice.proto +// frameworks/base/cmds/statsd/src/atoms.proto +message BlobLeaseeListProto { + repeated BlobLeaseeProto leasee = 1; +} + +/** + * Logs the current state of a Blob committed with BlobStoreManager + * + * Pulled from: + * frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java + */ +message BlobInfo { + // Id of the Blob + optional int64 blob_id = 1; + + // Size of the Blob data + optional int64 size = 2; + + // Unix epoch timestamp of the Blob's expiration in milliseconds + optional int64 expiry_timestamp_millis = 3; + + // List of committers of this Blob + optional BlobCommitterListProto committers = 4; + + // List of leasees of this Blob + optional BlobLeaseeListProto leasees = 5; +} diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp index 9df4d1f8ce24af8cee201538fe3dba9767566400..bb5d0a6bab58ddfad807746a9477ee5ba1ba3b4f 100644 --- a/cmds/statsd/src/external/StatsPuller.cpp +++ b/cmds/statsd/src/external/StatsPuller.cpp @@ -44,7 +44,8 @@ StatsPuller::StatsPuller(const int tagId, const int64_t coolDownNs, const int64_ bool StatsPuller::Pull(const int64_t eventTimeNs, std::vector>* data) { lock_guard lock(mLock); - int64_t elapsedTimeNs = getElapsedRealtimeNs(); + const int64_t elapsedTimeNs = getElapsedRealtimeNs(); + const int64_t systemUptimeMillis = getSystemUptimeMillis(); StatsdStats::getInstance().notePull(mTagId); const bool shouldUseCache = (mLastEventTimeNs == eventTimeNs) || (elapsedTimeNs - mLastPullTimeNs < mCoolDownNs); @@ -67,16 +68,18 @@ bool StatsPuller::Pull(const int64_t eventTimeNs, std::vector mPullTimeoutNs; + const int64_t pullElapsedDurationNs = getElapsedRealtimeNs() - elapsedTimeNs; + const int64_t pullSystemUptimeDurationMillis = getSystemUptimeMillis() - systemUptimeMillis; + StatsdStats::getInstance().notePullTime(mTagId, pullElapsedDurationNs); + const bool pullTimeOut = pullElapsedDurationNs > mPullTimeoutNs; if (pullTimeOut) { // Something went wrong. Discard the data. mCachedData.clear(); mHasGoodData = false; - StatsdStats::getInstance().notePullTimeout(mTagId); + StatsdStats::getInstance().notePullTimeout( + mTagId, pullSystemUptimeDurationMillis, NanoToMillis(pullElapsedDurationNs)); ALOGW("Pull for atom %d exceeds timeout %lld nano seconds.", mTagId, - (long long)pullDurationNs); + (long long)pullElapsedDurationNs); return mHasGoodData; } diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index c027fffd20a0e32205c78dd51496754cfd06de2b..6e89038f415208be27b084b692e3a0c9061d33ec 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -38,6 +38,7 @@ using android::util::ProtoOutputStream; using std::lock_guard; using std::shared_ptr; using std::string; +using std::to_string; using std::vector; const int FIELD_ID_BEGIN_TIME = 1; @@ -436,9 +437,18 @@ void StatsdStats::notePullDataError(int pullAtomId) { mPulledAtomStats[pullAtomId].dataError++; } -void StatsdStats::notePullTimeout(int pullAtomId) { +void StatsdStats::notePullTimeout(int pullAtomId, + int64_t pullUptimeMillis, + int64_t pullElapsedMillis) { lock_guard lock(mLock); - mPulledAtomStats[pullAtomId].pullTimeout++; + PulledAtomStats& pulledAtomStats = mPulledAtomStats[pullAtomId]; + pulledAtomStats.pullTimeout++; + + if (pulledAtomStats.pullTimeoutMetadata.size() == kMaxTimestampCount) { + pulledAtomStats.pullTimeoutMetadata.pop_front(); + } + + pulledAtomStats.pullTimeoutMetadata.emplace_back(pullUptimeMillis, pullElapsedMillis); } void StatsdStats::notePullExceedMaxDelay(int pullAtomId) { @@ -630,6 +640,7 @@ void StatsdStats::resetInternalLocked() { pullStats.second.unregisteredCount = 0; pullStats.second.atomErrorCount = 0; pullStats.second.binderCallFailCount = 0; + pullStats.second.pullTimeoutMetadata.clear(); } mAtomMetricStats.clear(); mActivationBroadcastGuardrailStats.clear(); @@ -786,6 +797,20 @@ void StatsdStats::dumpStats(int out) const { pair.second.pullUidProviderNotFound, pair.second.pullerNotFound, pair.second.registeredCount, pair.second.unregisteredCount, pair.second.atomErrorCount); + if (pair.second.pullTimeoutMetadata.size() > 0) { + string uptimeMillis = "(pull timeout system uptime millis) "; + string pullTimeoutMillis = "(pull timeout elapsed time millis) "; + for (const auto& stats : pair.second.pullTimeoutMetadata) { + uptimeMillis.append(to_string(stats.pullTimeoutUptimeMillis)).append(",");; + pullTimeoutMillis.append(to_string(stats.pullTimeoutElapsedMillis)).append(","); + } + uptimeMillis.pop_back(); + uptimeMillis.push_back('\n'); + pullTimeoutMillis.pop_back(); + pullTimeoutMillis.push_back('\n'); + dprintf(out, "%s", uptimeMillis.c_str()); + dprintf(out, "%s", pullTimeoutMillis.c_str()); + } } if (mAnomalyAlarmRegisteredStats > 0) { diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 8587e1452543e6fa42de8f5c0ea9724e3804771d..005048446fc31475c47ed57254cdf35d204e0769 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -101,7 +101,7 @@ public: // Per atom dimension key size limit static const std::map> kAtomDimensionKeySizeLimitMap; - const static int kMaxConfigCountPerUid = 10; + const static int kMaxConfigCountPerUid = 20; const static int kMaxAlertCountPerConfig = 100; const static int kMaxConditionCountPerConfig = 300; const static int kMaxMetricCountPerConfig = 1000; @@ -352,7 +352,7 @@ public: /* * Records pull exceeds timeout for the puller. */ - void notePullTimeout(int pullAtomId); + void notePullTimeout(int pullAtomId, int64_t pullUptimeMillis, int64_t pullElapsedMillis); /* * Records pull exceeds max delay for a metric. @@ -498,6 +498,14 @@ public: */ void dumpStats(int outFd) const; + typedef struct PullTimeoutMetadata { + int64_t pullTimeoutUptimeMillis; + int64_t pullTimeoutElapsedMillis; + PullTimeoutMetadata(int64_t uptimeMillis, int64_t elapsedMillis) : + pullTimeoutUptimeMillis(uptimeMillis), + pullTimeoutElapsedMillis(elapsedMillis) {/* do nothing */} + } PullTimeoutMetadata; + typedef struct { long totalPull = 0; long totalPullFromCache = 0; @@ -519,6 +527,7 @@ public: long unregisteredCount = 0; int32_t atomErrorCount = 0; long binderCallFailCount = 0; + std::list pullTimeoutMetadata; } PulledAtomStats; typedef struct { @@ -660,6 +669,8 @@ private: FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats); FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit); FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats); + + FRIEND_TEST(StatsLogProcessorTest, InvalidConfigRemoved); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index cdd20cdd70f9cd0c151c1c0aa7242fe29beda025..fe143e496373219a01ac9ff68e2721e609d6fdb0 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -98,7 +98,7 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo // Stores atom id to primary key pairs for each state atom that the metric is // sliced by. - std::map statePrimaryKeys; + std::map statePrimaryKeys; // For states with primary fields, use MetricStateLinks to get the primary // field values from the log event. These values will form a primary key diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index e8c575a1adea7c6e1b7098b9bf2c90691c8d5af5..60de1a24cce565f72d8beb70f35fc5bf7fed8d4a 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -21,7 +21,6 @@ #include #include "CountMetricProducer.h" -#include "atoms_info.h" #include "condition/CombinationConditionTracker.h" #include "condition/SimpleConditionTracker.h" #include "guardrail/StatsdStats.h" @@ -361,20 +360,17 @@ void MetricsManager::onDumpReport(const int64_t dumpTimeStampNs, protoOutput->end(token); } - mLastReportTimeNs = dumpTimeStampNs; - mLastReportWallClockNs = getWallClockNs(); + // Do not update the timestamps when data is not cleared to avoid timestamps from being + // misaligned. + if (erase_data) { + mLastReportTimeNs = dumpTimeStampNs; + mLastReportWallClockNs = getWallClockNs(); + } VLOG("=========================Metric Reports End=========================="); } bool MetricsManager::checkLogCredentials(const LogEvent& event) { - // TODO(b/154856835): Remove this check once we get whitelist from the config. - if (android::util::AtomsInfo::kWhitelistedAtoms.find(event.GetTagId()) != - android::util::AtomsInfo::kWhitelistedAtoms.end()) - { - return true; - } - if (mWhitelistedAtomIds.find(event.GetTagId()) != mWhitelistedAtomIds.end()) { return true; } diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index c0d1174023143f3deedaae2d0376af08645f5f1a..5987a723a421044a4f5c68b2b20e633fa588e506 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -189,11 +189,6 @@ void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId, VLOG("ValueMetric %lld onStateChanged time %lld, State %d, key %s, %d -> %d", (long long)mMetricId, (long long)eventTimeNs, atomId, primaryKey.toString().c_str(), oldState.mValue.int_value, newState.mValue.int_value); - // If condition is not true or metric is not active, we do not need to pull - // for this state change. - if (mCondition != ConditionState::kTrue || !mIsActive) { - return; - } // If old and new states are in the same StateGroup, then we do not need to // pull for this state change. @@ -205,6 +200,12 @@ void ValueMetricProducer::onStateChanged(int64_t eventTimeNs, int32_t atomId, return; } + // If condition is not true or metric is not active, we do not need to pull + // for this state change. + if (mCondition != ConditionState::kTrue || !mIsActive) { + return; + } + bool isEventLate = eventTimeNs < mCurrentBucketStartTimeNs; if (isEventLate) { VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, @@ -412,7 +413,6 @@ void ValueMetricProducer::resetBase() { for (auto& slice : mCurrentBaseInfo) { for (auto& baseInfo : slice.second) { baseInfo.hasBase = false; - baseInfo.hasCurrentState = false; } } mHasGlobalBase = false; @@ -625,7 +625,6 @@ void ValueMetricProducer::accumulateEvents(const std::vectorsecond) { baseInfo.hasBase = false; - baseInfo.hasCurrentState = false; } } } @@ -820,6 +819,8 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( Interval& interval = intervals[i]; interval.valueIndex = i; Value value; + baseInfo.hasCurrentState = true; + baseInfo.currentState = stateKey; if (!getDoubleOrLong(event, matcher, value)) { VLOG("Failed to get value %d from event %s", i, event.ToString().c_str()); StatsdStats::getInstance().noteBadValueType(mMetricId); @@ -907,7 +908,6 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked( interval.hasValue = true; } interval.sampleSize += 1; - baseInfo.currentState = stateKey; } // Only trigger the tracker if all intervals are correct @@ -951,6 +951,7 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, const int64_t& nextBucketStartTimeNs) { if (mCondition == ConditionState::kUnknown) { StatsdStats::getInstance().noteBucketUnknownCondition(mMetricId); + invalidateCurrentBucketWithoutResetBase(eventTimeNs, BucketDropReason::CONDITION_UNKNOWN); } VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs, @@ -959,7 +960,10 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); int64_t bucketEndTime = fullBucketEndTimeNs; int64_t numBucketsForward = calcBucketsForwardCount(eventTimeNs); - if (numBucketsForward > 1) { + + // Skip buckets if this is a pulled metric or a pushed metric that is diffed. + if (numBucketsForward > 1 && (mIsPulled || mUseDiff)) { + VLOG("Skipping forward %lld buckets", (long long)numBucketsForward); StatsdStats::getInstance().noteSkippedForwardBuckets(mMetricId); // Something went wrong. Maybe the device was sleeping for a long time. It is better diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 3de5b99a2b090e7e896c0fc5ea359b1900be5855..b359af745c91fa5c26073354569f8c7b6c8a28b3 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -75,7 +75,7 @@ public: if (!mSplitBucketForAppUpgrade) { return; } - if (mIsPulled && mCondition) { + if (mIsPulled && mCondition == ConditionState::kTrue) { pullAndMatchEventsLocked(eventTimeNs); } flushCurrentBucketLocked(eventTimeNs, eventTimeNs); @@ -84,7 +84,7 @@ public: // ValueMetric needs special logic if it's a pulled atom. void onStatsdInitCompleted(const int64_t& eventTimeNs) override { std::lock_guard lock(mMutex); - if (mIsPulled && mCondition) { + if (mIsPulled && mCondition == ConditionState::kTrue) { pullAndMatchEventsLocked(eventTimeNs); } flushCurrentBucketLocked(eventTimeNs, eventTimeNs); @@ -313,6 +313,7 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestSlicedState); FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithMap); FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions); + FRIEND_TEST(ValueMetricProducerTest, TestSlicedStateWithCondition); FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey); FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase); FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures); diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 6bfa26761b2f7fff448eac0a07816313d60526c9..ddd2725c9cb976803de60bfbe5b04c3605539fc0 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -467,6 +467,11 @@ message StatsdStatsReport { optional int64 binder_call_failed = 19; optional int64 failed_uid_provider_not_found = 20; optional int64 puller_not_found = 21; + message PullTimeoutMetadata { + optional int64 pull_timeout_uptime_millis = 1; + optional int64 pull_timeout_elapsed_millis = 2; + } + repeated PullTimeoutMetadata pull_atom_metadata = 22; } repeated PulledAtomStats pulled_atom_stats = 10; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index bafdfcba59b2386b67e1fa39f71ead394a69e018..423bae8bc0a41d1f705dbe4d3f59b81d9d77efba 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -81,6 +81,9 @@ const int FIELD_ID_ATOM_ERROR_COUNT = 18; const int FIELD_ID_BINDER_CALL_FAIL_COUNT = 19; const int FIELD_ID_PULL_UID_PROVIDER_NOT_FOUND = 20; const int FIELD_ID_PULLER_NOT_FOUND = 21; +const int FIELD_ID_PULL_TIMEOUT_METADATA = 22; +const int FIELD_ID_PULL_TIMEOUT_METADATA_UPTIME_MILLIS = 1; +const int FIELD_ID_PULL_TIMEOUT_METADATA_ELAPSED_MILLIS = 2; // for AtomMetricStats proto const int FIELD_ID_ATOM_METRIC_STATS = 17; @@ -497,6 +500,16 @@ void writePullerStatsToStream(const std::pair (long long)pair.second.pullUidProviderNotFound); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULLER_NOT_FOUND, (long long)pair.second.pullerNotFound); + for (const auto& pullTimeoutMetadata : pair.second.pullTimeoutMetadata) { + uint64_t timeoutMetadataToken = protoOutput->start(FIELD_TYPE_MESSAGE | + FIELD_ID_PULL_TIMEOUT_METADATA | + FIELD_COUNT_REPEATED); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT_METADATA_UPTIME_MILLIS, + pullTimeoutMetadata.pullTimeoutUptimeMillis); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_TIMEOUT_METADATA_ELAPSED_MILLIS, + pullTimeoutMetadata.pullTimeoutElapsedMillis); + protoOutput->end(timeoutMetadataToken); + } protoOutput->end(token); } @@ -542,6 +555,10 @@ int64_t getElapsedRealtimeMillis() { return ::android::elapsedRealtime(); } +int64_t getSystemUptimeMillis() { + return ::android::uptimeMillis(); +} + int64_t getWallClockNs() { return time(nullptr) * NS_PER_SEC; } diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index 20d93b5a53654c42efa38aaba82ddea0626e8c2c..eb65dc6979c54b521264474631cdc2c7b6abd6df 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -61,6 +61,9 @@ int64_t getElapsedRealtimeMillis(); // Gets the elapsed timestamp in seconds. int64_t getElapsedRealtimeSec(); +// Gets the system uptime in millis. +int64_t getSystemUptimeMillis(); + // Gets the wall clock timestamp in ns. int64_t getWallClockNs(); diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 72decf2c7bd00604baa446e0876dbde08531a867..acdffd3d4712c7ac983b86d624d33e5508945c35 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -198,6 +198,9 @@ message EventMetric { optional int64 condition = 3; repeated MetricConditionLink links = 4; + + reserved 100; + reserved 101; } message CountMetric { @@ -218,6 +221,9 @@ message CountMetric { repeated MetricStateLink state_link = 9; optional FieldMatcher dimensions_in_condition = 7 [deprecated = true]; + + reserved 100; + reserved 101; } message DurationMetric { @@ -245,6 +251,9 @@ message DurationMetric { optional TimeUnit bucket = 7; optional FieldMatcher dimensions_in_condition = 8 [deprecated = true]; + + reserved 100; + reserved 101; } message GaugeMetric { @@ -281,6 +290,9 @@ message GaugeMetric { optional int32 max_pull_delay_sec = 13 [default = 30]; optional bool split_bucket_for_app_upgrade = 14 [default = true]; + + reserved 100; + reserved 101; } message ValueMetric { @@ -333,6 +345,9 @@ message ValueMetric { optional bool split_bucket_for_app_upgrade = 17 [default = true]; optional FieldMatcher dimensions_in_condition = 9 [deprecated = true]; + + reserved 100; + reserved 101; } message Alert { diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp index 23f8ca4e74e60518bdf7d0e6f55185127a42f54b..a21eb9b9147ff56a2c7842f2d20028dfa194805b 100644 --- a/cmds/statsd/tests/FieldValue_test.cpp +++ b/cmds/statsd/tests/FieldValue_test.cpp @@ -33,6 +33,12 @@ namespace android { namespace os { namespace statsd { +// These constants must be kept in sync with those in StatsDimensionsValue.java. +const static int STATS_DIMENSIONS_VALUE_STRING_TYPE = 2; +const static int STATS_DIMENSIONS_VALUE_INT_TYPE = 3; +const static int STATS_DIMENSIONS_VALUE_FLOAT_TYPE = 6; +const static int STATS_DIMENSIONS_VALUE_TUPLE_TYPE = 7; + namespace { void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, const vector& attributionUids, const vector& attributionTags, @@ -291,34 +297,76 @@ TEST(AtomMatcherTest, TestWriteDimensionPath) { } } -//TODO(b/149050405) Update this test for StatsDimensionValueParcel -//TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { -// HashableDimensionKey dim; -// -// int pos1[] = {1, 1, 1}; -// int pos2[] = {1, 1, 2}; -// int pos3[] = {1, 1, 3}; -// int pos4[] = {2, 0, 0}; -// -// Field field1(10, pos1, 2); -// Field field2(10, pos2, 2); -// Field field3(10, pos3, 2); -// Field field4(10, pos4, 0); -// -// Value value1((int32_t)10025); -// Value value2("tag"); -// Value value3((int32_t)987654); -// Value value4((int32_t)99999); -// -// dim.addValue(FieldValue(field1, value1)); -// dim.addValue(FieldValue(field2, value2)); -// dim.addValue(FieldValue(field3, value3)); -// dim.addValue(FieldValue(field4, value4)); -// -// SubscriberReporter::getStatsDimensionsValue(dim); -// // TODO(b/110562792): can't test anything here because StatsDimensionsValue class doesn't -// // have any read api. -//} +void checkAttributionNodeInDimensionsValueParcel(StatsDimensionsValueParcel& attributionNodeParcel, + int32_t nodeDepthInAttributionChain, + int32_t uid, string tag) { + EXPECT_EQ(attributionNodeParcel.field, nodeDepthInAttributionChain /*position at depth 1*/); + ASSERT_EQ(attributionNodeParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(attributionNodeParcel.tupleValue.size(), 2); + + StatsDimensionsValueParcel uidParcel = attributionNodeParcel.tupleValue[0]; + EXPECT_EQ(uidParcel.field, 1 /*position at depth 2*/); + EXPECT_EQ(uidParcel.valueType, STATS_DIMENSIONS_VALUE_INT_TYPE); + EXPECT_EQ(uidParcel.intValue, uid); + + StatsDimensionsValueParcel tagParcel = attributionNodeParcel.tupleValue[1]; + EXPECT_EQ(tagParcel.field, 2 /*position at depth 2*/); + EXPECT_EQ(tagParcel.valueType, STATS_DIMENSIONS_VALUE_STRING_TYPE); + EXPECT_EQ(tagParcel.stringValue, tag); +} + +// Test conversion of a HashableDimensionKey into a StatsDimensionValueParcel +TEST(AtomMatcherTest, TestSubscriberDimensionWrite) { + int atomId = 10; + // First four fields form an attribution chain + int pos1[] = {1, 1, 1}; + int pos2[] = {1, 1, 2}; + int pos3[] = {1, 2, 1}; + int pos4[] = {1, 2, 2}; + int pos5[] = {2, 1, 1}; + + Field field1(atomId, pos1, /*depth=*/2); + Field field2(atomId, pos2, /*depth=*/2); + Field field3(atomId, pos3, /*depth=*/2); + Field field4(atomId, pos4, /*depth=*/2); + Field field5(atomId, pos5, /*depth=*/0); + + Value value1((int32_t)1); + Value value2("string2"); + Value value3((int32_t)3); + Value value4("string4"); + Value value5((float)5.0); + + HashableDimensionKey dimensionKey; + dimensionKey.addValue(FieldValue(field1, value1)); + dimensionKey.addValue(FieldValue(field2, value2)); + dimensionKey.addValue(FieldValue(field3, value3)); + dimensionKey.addValue(FieldValue(field4, value4)); + dimensionKey.addValue(FieldValue(field5, value5)); + + StatsDimensionsValueParcel rootParcel = dimensionKey.toStatsDimensionsValueParcel(); + EXPECT_EQ(rootParcel.field, atomId); + ASSERT_EQ(rootParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(rootParcel.tupleValue.size(), 2); + + // Check that attribution chain is populated correctly + StatsDimensionsValueParcel attributionChainParcel = rootParcel.tupleValue[0]; + EXPECT_EQ(attributionChainParcel.field, 1 /*position at depth 0*/); + ASSERT_EQ(attributionChainParcel.valueType, STATS_DIMENSIONS_VALUE_TUPLE_TYPE); + ASSERT_EQ(attributionChainParcel.tupleValue.size(), 2); + checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[0], + /*nodeDepthInAttributionChain=*/1, + value1.int_value, value2.str_value); + checkAttributionNodeInDimensionsValueParcel(attributionChainParcel.tupleValue[1], + /*nodeDepthInAttributionChain=*/2, + value3.int_value, value4.str_value); + + // Check that the float is populated correctly + StatsDimensionsValueParcel floatParcel = rootParcel.tupleValue[1]; + EXPECT_EQ(floatParcel.field, 2 /*position at depth 0*/); + EXPECT_EQ(floatParcel.valueType, STATS_DIMENSIONS_VALUE_FLOAT_TYPE); + EXPECT_EQ(floatParcel.floatValue, value5.float_value); +} TEST(AtomMatcherTest, TestWriteDimensionToProto) { HashableDimensionKey dim; diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 076f32752223e6b28ac116686bd23ff8d9bdeb13..1e6680c4756716269004e15e59a7be4e412f6c9e 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -13,6 +13,11 @@ // limitations under the License. #include "StatsLogProcessor.h" + +#include +#include +#include + #include "StatsService.h" #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" @@ -20,16 +25,10 @@ #include "guardrail/StatsdStats.h" #include "logd/LogEvent.h" #include "packages/UidMap.h" -#include "storage/StorageManager.h" #include "statslog_statsdtest.h" - -#include -#include - +#include "storage/StorageManager.h" #include "tests/statsd_test_util.h" -#include - using namespace android; using namespace testing; using ::ndk::SharedRefBase; @@ -324,6 +323,41 @@ TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate) { EXPECT_EQ(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end()); } +TEST(StatsLogProcessorTest, InvalidConfigRemoved) { + // Setup simple config key corresponding to empty config. + StatsdStats::getInstance().reset(); + sp m = new UidMap(); + sp pullerManager = new StatsPullerManager(); + m->updateMap(1, {1, 2}, {1, 2}, {String16("v1"), String16("v2")}, + {String16("p1"), String16("p2")}, {String16(""), String16("")}); + sp anomalyAlarmMonitor; + sp subscriberAlarmMonitor; + StatsLogProcessor p(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, + [](const ConfigKey& key) { return true; }, + [](const int&, const vector&) {return true;}); + ConfigKey key(3, 4); + StatsdConfig config = MakeConfig(true); + p.OnConfigUpdated(0, key, config); + EXPECT_EQ(1, p.mMetricsManagers.size()); + EXPECT_NE(p.mMetricsManagers.find(key), p.mMetricsManagers.end()); + // Cannot assert the size of mConfigStats since it is static and does not get cleared on reset. + EXPECT_NE(StatsdStats::getInstance().mConfigStats.end(), + StatsdStats::getInstance().mConfigStats.find(key)); + EXPECT_EQ(0, StatsdStats::getInstance().mIceBox.size()); + + StatsdConfig invalidConfig = MakeConfig(true); + invalidConfig.clear_allowed_log_source(); + p.OnConfigUpdated(0, key, invalidConfig); + EXPECT_EQ(0, p.mMetricsManagers.size()); + // The current configs should not contain the invalid config. + EXPECT_EQ(StatsdStats::getInstance().mConfigStats.end(), + StatsdStats::getInstance().mConfigStats.find(key)); + // Both "config" and "invalidConfig" should be in the icebox. + EXPECT_EQ(2, StatsdStats::getInstance().mIceBox.size()); + +} + + TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) { int uid = 1111; @@ -1796,6 +1830,53 @@ TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUidAttributionCha EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value); } +TEST(StatsLogProcessorTest, TestDumpReportWithoutErasingDataDoesNotUpdateTimestamp) { + int hostUid = 20; + int isolatedUid = 30; + sp mockUidMap = makeMockUidMapForOneHost(hostUid, {isolatedUid}); + ConfigKey key(3, 4); + + // TODO: All tests should not persist state on disk. This removes any reports that were present. + ProtoOutputStream proto; + StorageManager::appendConfigMetricsReport(key, &proto, /*erase data=*/true, /*isAdb=*/false); + + StatsdConfig config = MakeConfig(false); + sp processor = + CreateStatsLogProcessor(1, 1, config, key, nullptr, 0, mockUidMap); + vector bytes; + + int64_t dumpTime1Ns = 1 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime1Ns, false /* include_current_bucket */, + true /* erase_data */, ADB_DUMP, FAST, &bytes); + + ConfigMetricsReportList output; + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime1Ns); + + int64_t dumpTime2Ns = 5 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime2Ns, false /* include_current_bucket */, + false /* erase_data */, ADB_DUMP, FAST, &bytes); + + // Check that the dump report without clearing data is successful. + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime2Ns); + EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns); + + int64_t dumpTime3Ns = 10 * NS_PER_SEC; + processor->onDumpReport(key, dumpTime3Ns, false /* include_current_bucket */, + true /* erase_data */, ADB_DUMP, FAST, &bytes); + + // Check that the previous dump report that didn't clear data did not overwrite the first dump's + // timestamps. + output.ParseFromArray(bytes.data(), bytes.size()); + EXPECT_EQ(output.reports_size(), 1); + EXPECT_EQ(output.reports(0).current_report_elapsed_nanos(), dumpTime3Ns); + EXPECT_EQ(output.reports(0).last_report_elapsed_nanos(), dumpTime1Ns); + +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp index 322cfaf68a41a1088a5a94910a2b7b6f0c339f21..64ea219c84656d87ac1e8530b09b78a6b5239718 100644 --- a/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp +++ b/cmds/statsd/tests/anomaly/AlarmTracker_test.cpp @@ -43,23 +43,47 @@ TEST(AlarmTrackerTest, TestTriggerTimestamp) { alarm.set_offset_millis(15 * MS_PER_SEC); alarm.set_period_millis(60 * 60 * MS_PER_SEC); // 1hr int64_t startMillis = 100000000 * MS_PER_SEC; + int64_t nextAlarmTime = startMillis / MS_PER_SEC + 15; AlarmTracker tracker(startMillis, startMillis, alarm, kConfigKey, subscriberAlarmMonitor); - EXPECT_EQ(tracker.mAlarmSec, (int64_t)(startMillis / MS_PER_SEC + 15)); + EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); uint64_t currentTimeSec = startMillis / MS_PER_SEC + 10; std::unordered_set, SpHash> firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); EXPECT_TRUE(firedAlarmSet.empty()); tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); - EXPECT_EQ(tracker.mAlarmSec, (int64_t)(startMillis / MS_PER_SEC + 15)); + EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); + EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); currentTimeSec = startMillis / MS_PER_SEC + 7000; + nextAlarmTime = startMillis / MS_PER_SEC + 15 + 2 * 60 * 60; firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); ASSERT_EQ(firedAlarmSet.size(), 1u); tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); EXPECT_TRUE(firedAlarmSet.empty()); - EXPECT_EQ(tracker.mAlarmSec, (int64_t)(startMillis / MS_PER_SEC + 15 + 2 * 60 * 60)); + EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); + EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); + + // Alarm fires exactly on time. + currentTimeSec = startMillis / MS_PER_SEC + 15 + 2 * 60 * 60; + nextAlarmTime = startMillis / MS_PER_SEC + 15 + 3 * 60 * 60; + firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); + ASSERT_EQ(firedAlarmSet.size(), 1u); + tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); + EXPECT_TRUE(firedAlarmSet.empty()); + EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); + EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); + + // Alarm fires exactly 1 period late. + currentTimeSec = startMillis / MS_PER_SEC + 15 + 4 * 60 * 60; + nextAlarmTime = startMillis / MS_PER_SEC + 15 + 5 * 60 * 60; + firedAlarmSet = subscriberAlarmMonitor->popSoonerThan(static_cast(currentTimeSec)); + ASSERT_EQ(firedAlarmSet.size(), 1u); + tracker.informAlarmsFired(currentTimeSec * NS_PER_SEC, firedAlarmSet); + EXPECT_TRUE(firedAlarmSet.empty()); + EXPECT_EQ(tracker.mAlarmSec, nextAlarmTime); + EXPECT_EQ(tracker.getAlarmTimestampSec(), nextAlarmTime); } } // namespace statsd diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 5cc10cd9840ce996029f2e7cef55365e7211b3e8..428c46f8a0d2ad0f19e65f14b075f813774f65ec 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -306,6 +306,8 @@ TEST(StatsdStatsTest, TestPullAtomStats) { stats.notePullUidProviderNotFound(util::DISK_SPACE); stats.notePullerNotFound(util::DISK_SPACE); stats.notePullerNotFound(util::DISK_SPACE); + stats.notePullTimeout(util::DISK_SPACE, 3000L, 6000L); + stats.notePullTimeout(util::DISK_SPACE, 4000L, 7000L); vector output; stats.dumpStats(&output, false); @@ -328,6 +330,13 @@ TEST(StatsdStatsTest, TestPullAtomStats) { EXPECT_EQ(1L, report.pulled_atom_stats(0).binder_call_failed()); EXPECT_EQ(1L, report.pulled_atom_stats(0).failed_uid_provider_not_found()); EXPECT_EQ(2L, report.pulled_atom_stats(0).puller_not_found()); + ASSERT_EQ(2, report.pulled_atom_stats(0).pull_atom_metadata_size()); + EXPECT_EQ(3000L, report.pulled_atom_stats(0).pull_atom_metadata(0).pull_timeout_uptime_millis()); + EXPECT_EQ(4000L, report.pulled_atom_stats(0).pull_atom_metadata(1).pull_timeout_uptime_millis()); + EXPECT_EQ(6000L, report.pulled_atom_stats(0).pull_atom_metadata(0) + .pull_timeout_elapsed_millis()); + EXPECT_EQ(7000L, report.pulled_atom_stats(0).pull_atom_metadata(1) + .pull_timeout_elapsed_millis()); } TEST(StatsdStatsTest, TestAtomMetricsStats) { diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 1bcc35d99d18f4a4f100f0eb0fe27c9ef19dde26..5666501d7d510fa50fd479e8d15e954e09f936b4 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -167,6 +167,32 @@ public: return valueProducer; } + static sp createValueProducerWithConditionAndState( + sp& pullerManager, ValueMetric& metric, + vector slicedStateAtoms, + unordered_map> stateGroupMap, + ConditionState conditionAfterFirstBucketPrepared) { + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp wizard = new NaggyMock(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)) + .WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)) + .WillRepeatedly(Return()); + + sp valueProducer = new ValueMetricProducer( + kConfigKey, metric, 0 /* condition tracker index */, {ConditionState::kUnknown}, + wizard, logEventMatcherIndex, eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager, {}, {}, slicedStateAtoms, stateGroupMap); + valueProducer->prepareFirstBucket(); + valueProducer->mCondition = conditionAfterFirstBucketPrepared; + return valueProducer; + } + static ValueMetric createMetric() { ValueMetric metric; metric.set_id(metricId); @@ -188,6 +214,13 @@ public: metric.add_slice_by_state(StringToId(state)); return metric; } + + static ValueMetric createMetricWithConditionAndState(string state) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + metric.set_condition(StringToId("SCREEN_ON")); + metric.add_slice_by_state(StringToId(state)); + return metric; + } }; // Setup for parameterized tests. @@ -3262,11 +3295,15 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenConditionEventWron report.value_metrics().skipped(0).start_bucket_elapsed_millis()); EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), report.value_metrics().skipped(0).end_bucket_elapsed_millis()); - ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + ASSERT_EQ(2, report.value_metrics().skipped(0).drop_event_size()); auto dropEvent = report.value_metrics().skipped(0).drop_event(0); EXPECT_EQ(BucketDropReason::EVENT_IN_WRONG_BUCKET, dropEvent.drop_reason()); EXPECT_EQ(NanoToMillis(bucket2StartTimeNs - 100), dropEvent.drop_time_millis()); + + dropEvent = report.value_metrics().skipped(0).drop_event(1); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs + 100), dropEvent.drop_time_millis()); } /* @@ -3582,7 +3619,7 @@ TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) { sp valueProducer = ValueMetricProducerTestHelper::createValueProducerWithCondition( - pullerManager, metric, ConditionState::kUnknown); + pullerManager, metric, ConditionState::kFalse); // Check dump report. ProtoOutputStream output; @@ -3607,6 +3644,94 @@ TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) { EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis()); } +/* + * Test that all buckets are dropped due to condition unknown until the first onConditionChanged. + */ +TEST(ValueMetricProducerTest_BucketDrop, TestConditionUnknownMultipleBuckets) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition change to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 10)); + return true; + })) + // Dump report requested. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 15 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 15 * NS_PER_SEC, 15)); + return true; + })); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, ConditionState::kUnknown); + + // Bucket should be dropped because of condition unknown. + int64_t appUpgradeTimeNs = bucketStartTimeNs + 5 * NS_PER_SEC; + valueProducer->notifyAppUpgrade(appUpgradeTimeNs); + + // Bucket also dropped due to condition unknown + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 3)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // This bucket is also dropped due to condition unknown. + int64_t conditionChangeTimeNs = bucket2StartTimeNs + 10 * NS_PER_SEC; + valueProducer->onConditionChanged(true, conditionChangeTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucket2StartTimeNs + 15 * NS_PER_SEC; // 15 seconds + valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(3, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), dropEvent.drop_time_millis()); + + EXPECT_EQ(NanoToMillis(appUpgradeTimeNs), + report.value_metrics().skipped(1).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(1).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size()); + + dropEvent = report.value_metrics().skipped(1).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), dropEvent.drop_time_millis()); + + EXPECT_EQ(NanoToMillis(bucket2StartTimeNs), + report.value_metrics().skipped(2).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(dumpReportTimeNs), + report.value_metrics().skipped(2).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(2).drop_event_size()); + + dropEvent = report.value_metrics().skipped(2).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(conditionChangeTimeNs), dropEvent.drop_time_millis()); +} + /* * Test that a skipped bucket is logged when a forced bucket split occurs when the previous bucket * was not flushed in time. @@ -3893,13 +4018,13 @@ TEST(ValueMetricProducerTest, TestSlicedState) { return true; })); + StateManager::getInstance().clear(); sp valueProducer = ValueMetricProducerTestHelper::createValueProducerWithState( pullerManager, metric, {util::SCREEN_STATE_CHANGED}, {}); EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().clear(); StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer); EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); @@ -4105,12 +4230,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithMap) { } } + StateManager::getInstance().clear(); sp valueProducer = ValueMetricProducerTestHelper::createValueProducerWithState( pullerManager, metric, {util::SCREEN_STATE_CHANGED}, stateGroupMap); // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().clear(); StateManager::getInstance().registerListener(SCREEN_STATE_ATOM_ID, valueProducer); EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID)); @@ -4357,12 +4482,12 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { return true; })); + StateManager::getInstance().clear(); sp valueProducer = ValueMetricProducerTestHelper::createValueProducerWithState( pullerManager, metric, {UID_PROCESS_STATE_ATOM_ID}, {}); // Set up StateManager and check that StateTrackers are initialized. - StateManager::getInstance().clear(); StateManager::getInstance().registerListener(UID_PROCESS_STATE_ATOM_ID, valueProducer); EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID)); @@ -4722,6 +4847,212 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long()); } +TEST(ValueMetricProducerTest, TestSlicedStateWithCondition) { + // Set up ValueMetricProducer. + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithConditionAndState( + "BATTERY_SAVER_MODE_STATE"); + sp pullerManager = new StrictMock(); + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _, _)) + // Condition changed to true. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 20 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 20 * NS_PER_SEC, 3)); + return true; + })) + // Battery saver mode state changed to OFF. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data, bool) { + EXPECT_EQ(eventTimeNs, bucketStartTimeNs + 30 * NS_PER_SEC); + data->clear(); + data->push_back( + CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 30 * NS_PER_SEC, 5)); + return true; + })) + // Condition changed to false. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector>* data, bool) { + EXPECT_EQ(eventTimeNs, bucket2StartTimeNs + 10 * NS_PER_SEC); + data->clear(); + data->push_back(CreateRepeatedValueLogEvent( + tagId, bucket2StartTimeNs + 10 * NS_PER_SEC, 15)); + return true; + })); + + StateManager::getInstance().clear(); + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithConditionAndState( + pullerManager, metric, {util::BATTERY_SAVER_MODE_STATE_CHANGED}, {}, + ConditionState::kFalse); + EXPECT_EQ(1, valueProducer->mSlicedStateAtoms.size()); + + // Set up StateManager and check that StateTrackers are initialized. + StateManager::getInstance().registerListener(util::BATTERY_SAVER_MODE_STATE_CHANGED, + valueProducer); + EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount()); + EXPECT_EQ(1, StateManager::getInstance().getListenersCount( + util::BATTERY_SAVER_MODE_STATE_CHANGED)); + + // Bucket status after battery saver mode ON event. + // Condition is false so we do nothing. + unique_ptr batterySaverOnEvent = + CreateBatterySaverOnEvent(/*timestamp=*/bucketStartTimeNs + 10 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOnEvent); + EXPECT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size()); + EXPECT_EQ(0UL, valueProducer->mCurrentBaseInfo.size()); + + // Bucket status after condition change to true. + valueProducer->onConditionChanged(true, bucketStartTimeNs + 20 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + std::unordered_map>::iterator + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(3, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {{}, -1} + ASSERT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); + std::unordered_map>::iterator + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(-1 /*StateTracker::kUnknown*/, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_FALSE(it->second[0].hasValue); + + // Bucket status after battery saver mode OFF event. + unique_ptr batterySaverOffEvent = + CreateBatterySaverOffEvent(/*timestamp=*/bucketStartTimeNs + 30 * NS_PER_SEC); + StateManager::getInstance().onLogEvent(*batterySaverOffEvent); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(5, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {{}, ON} + ASSERT_EQ(2UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(2, it->second[0].value.long_value); + + // Pull at end of first bucket. + vector> allData; + allData.clear(); + allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs, 11)); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + EXPECT_EQ(2UL, valueProducer->mPastBuckets.size()); + EXPECT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_TRUE(itBase->second[0].hasBase); + EXPECT_EQ(11, itBase->second[0].base.long_value); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + + // Bucket 2 status after condition change to false. + valueProducer->onConditionChanged(false, bucket2StartTimeNs + 10 * NS_PER_SEC); + // Base for dimension key {} + ASSERT_EQ(1UL, valueProducer->mCurrentBaseInfo.size()); + itBase = valueProducer->mCurrentBaseInfo.find(DEFAULT_DIMENSION_KEY); + EXPECT_FALSE(itBase->second[0].hasBase); + EXPECT_TRUE(itBase->second[0].hasCurrentState); + ASSERT_EQ(1, itBase->second[0].currentState.getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + itBase->second[0].currentState.getValues()[0].mValue.int_value); + // Value for key {{}, OFF} + ASSERT_EQ(3UL, valueProducer->mCurrentSlicedBucket.size()); + it = valueProducer->mCurrentSlicedBucket.begin(); + EXPECT_EQ(0, it->first.getDimensionKeyInWhat().getValues().size()); + ASSERT_EQ(1, it->first.getStateValuesKey().getValues().size()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, + it->first.getStateValuesKey().getValues()[0].mValue.int_value); + EXPECT_TRUE(it->second[0].hasValue); + EXPECT_EQ(4, it->second[0].value.long_value); + + // Start dump report and check output. + ProtoOutputStream output; + std::set strSet; + valueProducer->onDumpReport(bucket2StartTimeNs + 50 * NS_PER_SEC, + true /* include recent buckets */, true, NO_TIME_CONSTRAINTS, + &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(2, report.value_metrics().data_size()); + + ValueMetricData data = report.value_metrics().data(0); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(BatterySaverModeStateChanged::ON, data.slice_by_state(0).value()); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(2, data.bucket_info(0).values(0).value_long()); + + data = report.value_metrics().data(1); + EXPECT_EQ(util::BATTERY_SAVER_MODE_STATE_CHANGED, data.slice_by_state(0).atom_id()); + EXPECT_TRUE(data.slice_by_state(0).has_value()); + EXPECT_EQ(BatterySaverModeStateChanged::OFF, data.slice_by_state(0).value()); + ASSERT_EQ(2, data.bucket_info_size()); + EXPECT_EQ(6, data.bucket_info(0).values(0).value_long()); + EXPECT_EQ(4, data.bucket_info(1).values(0).value_long()); +} + +/* + * Test bucket splits when condition is unknown. + */ +TEST(ValueMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp pullerManager = new StrictMock(); + + sp valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, + ConditionState::kUnknown); + + // App update event. + int64_t appUpdateTimeNs = bucketStartTimeNs + 1000; + valueProducer->notifyAppUpgrade(appUpdateTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::CONDITION_UNKNOWN, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 06e29d4b5de172aed9734b484ae52788d56d0845..cee83725d075aa8387680a20b0bc69f1441e87b8 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -663,6 +663,8 @@ std::unique_ptr CreateBatterySaverOnEvent(uint64_t timestampNs) { AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); parseStatsEventToLogEvent(statsEvent, logEvent.get()); @@ -674,6 +676,8 @@ std::unique_ptr CreateBatterySaverOffEvent(uint64_t timestampNs) { AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED); AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_EXCLUSIVE_STATE, true); + AStatsEvent_addBoolAnnotation(statsEvent, util::ANNOTATION_ID_STATE_NESTED, false); std::unique_ptr logEvent = std::make_unique(/*uid=*/0, /*pid=*/0); parseStatsEventToLogEvent(statsEvent, logEvent.get()); diff --git a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp index db402a0dd658a2c2f630ac9089de97cb4bd60a53..32cecd3b9dbc393b31094b8e42df7a007f30880e 100644 --- a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp +++ b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp @@ -50,13 +50,13 @@ TEST(MultiConditionTrigger, TestMultipleConditions) { }); vector threads; - vector done(numConditions, false); + vector done(numConditions, 0); int i = 0; for (const string& conditionName : conditionNames) { threads.emplace_back([&done, &conditionName, &trigger, i] { sleep_for(chrono::milliseconds(3)); - done[i] = true; + done[i] = 1; trigger.markComplete(conditionName); }); i++; diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java index 6384fb12ca683b63063f999ce5a9c598c8143215..51bcad115cc54deee4bea1187749480cb5e8d728 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -342,6 +342,9 @@ public class TestDrive { .addPullAtomPackages(PullAtomPackages.newBuilder() .setAtomId(Atom.TRAIN_INFO_FIELD_NUMBER) .addPackages("AID_STATSD")) + .addPullAtomPackages(PullAtomPackages.newBuilder() + .setAtomId(Atom.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS_FIELD_NUMBER) + .addPackages("com.google.android.providers.media.module")) .setHashStringsInMetricReport(false); } } diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java index fe270a480d83f4d3a40a4f2e782c8cf3e99ecc37..fed9c43faa38ea90d1886e6f69f2fc4d37add643 100644 --- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java +++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java @@ -68,6 +68,8 @@ public final class Telecom extends BaseCommand { private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account"; private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer"; private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer"; + private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression"; + /** * Change the system dialer package name if a package name was specified, * Example: adb shell telecom set-system-dialer @@ -115,6 +117,8 @@ public final class Telecom extends BaseCommand { + "usage: telecom set-sim-count \n" + "usage: telecom get-sim-config\n" + "usage: telecom get-max-phones\n" + + "usage: telecom stop-block-suppression: Stop suppressing the blocked number" + + " provider after a call to emergency services.\n" + "usage: telecom set-emer-phone-account-filter \n" + "\n" + "telecom set-phone-account-enabled: Enables the given phone account, if it has" @@ -207,6 +211,9 @@ public final class Telecom extends BaseCommand { case COMMAND_UNREGISTER_PHONE_ACCOUNT: runUnregisterPhoneAccount(); break; + case COMMAND_STOP_BLOCK_SUPPRESSION: + runStopBlockSuppression(); + break; case COMMAND_SET_DEFAULT_DIALER: runSetDefaultDialer(); break; @@ -324,8 +331,13 @@ public final class Telecom extends BaseCommand { System.out.println("Success - " + handle + " unregistered."); } + private void runStopBlockSuppression() throws RemoteException { + mTelecomService.stopBlockSuppression(); + } + private void runSetDefaultDialer() throws RemoteException { - final String packageName = nextArgRequired(); + String packageName = nextArg(); + if ("default".equals(packageName)) packageName = null; mTelecomService.setTestDefaultDialer(packageName); System.out.println("Success - " + packageName + " set as override default dialer."); } diff --git a/config/preloaded-classes-blacklist b/config/preloaded-classes-blacklist index 353f786da1533d8bfdd15bc810d3c7f72017d330..48d579cc118e60b60a74ab0e44cecf868da861dc 100644 --- a/config/preloaded-classes-blacklist +++ b/config/preloaded-classes-blacklist @@ -1,6 +1,8 @@ android.content.AsyncTaskLoader$LoadTask android.net.ConnectivityThread$Singleton +android.os.AsyncTask android.os.FileObserver +android.os.NullVibrator android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask android.widget.Magnifier -sun.nio.fs.UnixChannelFactory +com.android.server.BootReceiver$2 diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index ed0ea556dc9d622ef45f7d3ca1d90b76c7250141..ac00a042b79e662a6196e556fac44738991a001d 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -520,6 +520,12 @@ public abstract class AccessibilityService extends Service { */ public static final int GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 13; + /** + * Action to show Launcher's all apps. + * @hide + */ + public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14; + private static final String LOG_TAG = "AccessibilityService"; /** diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 87fc8fe392f01468c618e44dbf0ca346f06d37bd..3772755beca18e8cb4e9f360ae2ab08bd69172b8 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2816,6 +2816,11 @@ public class Activity extends ContextThemeWrapper * The system may disallow entering picture-in-picture in various cases, including when the * activity is not visible, if the screen is locked or if the user has an activity pinned. * + *

By default, system calculates the dimension of picture-in-picture window based on the + * given {@param params}. + * See Picture-in-picture Support + * on how to override this behavior.

+ * * @see android.R.attr#supportsPictureInPicture * @see PictureInPictureParams * @@ -5151,6 +5156,10 @@ public class Activity extends ContextThemeWrapper * the signature of the app declaring the permissions. *

*

+ * Call {@link #shouldShowRequestPermissionRationale(String)} before calling this API to + * check if the system recommends to show a rationale UI before asking for a permission. + *

+ *

* If your app does not have the requested permissions the user will be presented * with UI for accepting them. After the user has accepted or rejected the * requested permissions you will receive a callback on {@link @@ -5240,20 +5249,10 @@ public class Activity extends ContextThemeWrapper } /** - * Gets whether you should show UI with rationale for requesting a permission. - * You should do this only if you do not have the permission and the context in - * which the permission is requested does not clearly communicate to the user - * what would be the benefit from granting this permission. - *

- * For example, if you write a camera app, requesting the camera permission - * would be expected by the user and no rationale for why it is requested is - * needed. If however, the app needs location for tagging photos then a non-tech - * savvy user may wonder how location is related to taking photos. In this case - * you may choose to show UI with rationale of requesting this permission. - *

+ * Gets whether you should show UI with rationale before requesting a permission. * * @param permission A permission your app wants to request. - * @return Whether you can show permission rationale UI. + * @return Whether you should show permission rationale UI. * * @see #checkSelfPermission(String) * @see #requestPermissions(String[], int) @@ -5568,7 +5567,7 @@ public class Activity extends ContextThemeWrapper options = transferSpringboardActivityOptions(options); String resolvedType = null; if (fillInIntent != null) { - fillInIntent.migrateExtraStreamToClipData(); + fillInIntent.migrateExtraStreamToClipData(this); fillInIntent.prepareToLeaveProcess(this); resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver()); } @@ -5823,7 +5822,7 @@ public class Activity extends ContextThemeWrapper if (referrer != null) { intent.putExtra(Intent.EXTRA_REFERRER, referrer); } - intent.migrateExtraStreamToClipData(); + intent.migrateExtraStreamToClipData(this); intent.prepareToLeaveProcess(this); result = ActivityTaskManager.getService() .startActivity(mMainThread.getApplicationThread(), getBasePackageName(), @@ -5894,7 +5893,7 @@ public class Activity extends ContextThemeWrapper @Nullable Bundle options) { if (mParent == null) { try { - intent.migrateExtraStreamToClipData(); + intent.migrateExtraStreamToClipData(this); intent.prepareToLeaveProcess(this); return ActivityTaskManager.getService() .startNextMatchingActivity(mToken, intent, options); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 97b704ccc1c9b8d1c229fbddd873fafeeab5a0ec..acf6315ddc5df736b4b275124566b7763f316535 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -159,10 +159,10 @@ public class ActivityManager { */ public static final int INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS = 1 << 0; /** - * Mount full external storage for the newly started instrumentation. + * Grant full access to the external storage for the newly started instrumentation. * @hide */ - public static final int INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL = 1 << 1; + public static final int INSTR_FLAG_DISABLE_ISOLATED_STORAGE = 1 << 1; /** * Disable test API access for the newly started instrumentation. @@ -601,20 +601,6 @@ public class ActivityManager { @TestApi public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2; - // TODO: remove this when development is done. - // These are debug flags used between OomAdjuster and AppOpsService to detect and report absence - // of the real flags. - /** @hide */ - public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q = 1 << 27; - /** @hide */ - public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q = 1 << 28; - /** @hide */ - public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29; - /** @hide */ - public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30; - /** @hide */ - public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31; - /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/ @TestApi public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION @@ -653,29 +639,9 @@ public class ActivityManager { */ public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) { printCapabilitiesSummary(pw, caps); - if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) { - pw.print(" !L"); - } - if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { - pw.print(" !C"); - } - if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q) != 0) { - pw.print(" !Cq"); - } - if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { - pw.print(" !M"); - } - if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q) != 0) { - pw.print(" !Mq"); - } final int remain = caps & ~(PROCESS_CAPABILITY_FOREGROUND_LOCATION | PROCESS_CAPABILITY_FOREGROUND_CAMERA - | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE - | DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION - | DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA - | DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q - | DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE - | DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q); + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE); if (remain != 0) { pw.print('+'); pw.print(remain); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index c491eea7a674ac399205d157dd0abc589f8c7158..a5965bc7f85fd6f538b0bca9053aedabe25e079c 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -398,13 +398,6 @@ public abstract class ActivityManagerInternal { */ public abstract boolean isUidCurrentlyInstrumented(int uid); - /** - * Show a debug toast, asking user to file a bugreport. - */ - // TODO: remove this toast after feature development is done - public abstract void showWhileInUseDebugToast(int uid, int op, int mode); - - /** Is this a device owner app? */ public abstract boolean isDeviceOwner(int uid); @@ -429,11 +422,17 @@ public abstract class ActivityManagerInternal { int userId, int[] appIdWhitelist); /** - * Add or delete uid from the ActivityManagerService PendingStartActivityUids list. + * Add uid to the ActivityManagerService PendingStartActivityUids list. + * @param uid uid + * @param pid pid of the ProcessRecord that is pending top. + */ + public abstract void addPendingTopUid(int uid, int pid); + + /** + * Delete uid from the ActivityManagerService PendingStartActivityUids list. * @param uid uid - * @param pending add to the list if true, delete from list if false. */ - public abstract void updatePendingTopUid(int uid, boolean pending); + public abstract void deletePendingTopUid(int uid); /** * Is the uid in ActivityManagerService PendingStartActivityUids list? diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 1cc63da3db0a62e333843472ac9411526946aae2..0f31529451fb3466fdb88776521fbb9671de213a 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -433,13 +433,21 @@ public class ActivityTaskManager { } } - /** Returns whether the current UI mode supports error dialogs (ANR, crash, etc). */ - public static boolean currentUiModeSupportsErrorDialogs(@NonNull Context context) { - final Configuration config = context.getResources().getConfiguration(); + /** + * @return whether the UI mode of the given config supports error dialogs (ANR, crash, etc). + * @hide + */ + public static boolean currentUiModeSupportsErrorDialogs(@NonNull Configuration config) { int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK; return (modeType != Configuration.UI_MODE_TYPE_CAR && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER) && modeType != Configuration.UI_MODE_TYPE_TELEVISION && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET); } + + /** @return whether the current UI mode supports error dialogs (ANR, crash, etc). */ + public static boolean currentUiModeSupportsErrorDialogs(@NonNull Context context) { + final Configuration config = context.getResources().getConfiguration(); + return currentUiModeSupportsErrorDialogs(config); + } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index cffa59c06a538f2a1ed6ee76a328453325295d01..812ca4aefb9b01fb9c55a80a410ea8a04a5335d4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -123,6 +123,7 @@ import android.os.SystemProperties; import android.os.TelephonyServiceManager; import android.os.Trace; import android.os.UserHandle; +import android.os.UserManager; import android.permission.IPermissionManager; import android.provider.BlockedNumberContract; import android.provider.CalendarContract; @@ -3252,18 +3253,56 @@ public final class ActivityThread extends ClientTransactionHandler { @Override public void handleFixedRotationAdjustments(@NonNull IBinder token, @Nullable FixedRotationAdjustments fixedRotationAdjustments) { - final Consumer override = fixedRotationAdjustments != null - ? displayAdjustments -> displayAdjustments.setFixedRotationAdjustments( - fixedRotationAdjustments) - : null; + handleFixedRotationAdjustments(token, fixedRotationAdjustments, null /* overrideConfig */); + } + + /** + * Applies the rotation adjustments to override display information in resources belong to the + * provided token. If the token is activity token, the adjustments also apply to application + * because the appearance of activity is usually more sensitive to the application resources. + * + * @param token The token to apply the adjustments. + * @param fixedRotationAdjustments The information to override the display adjustments of + * corresponding resources. If it is null, the exiting override + * will be cleared. + * @param overrideConfig The override configuration of activity. It is used to override + * application configuration. If it is non-null, it means the token is + * confirmed as activity token. Especially when launching new activity, + * {@link #mActivities} hasn't put the new token. + */ + private void handleFixedRotationAdjustments(@NonNull IBinder token, + @Nullable FixedRotationAdjustments fixedRotationAdjustments, + @Nullable Configuration overrideConfig) { + // The element of application configuration override is set only if the application + // adjustments are needed, because activity already has its own override configuration. + final Configuration[] appConfigOverride; + final Consumer override; + if (fixedRotationAdjustments != null) { + appConfigOverride = new Configuration[1]; + override = displayAdjustments -> { + displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments); + if (appConfigOverride[0] != null) { + displayAdjustments.getConfiguration().updateFrom(appConfigOverride[0]); + } + }; + } else { + appConfigOverride = null; + override = null; + } if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) { // No resources are associated with the token. return; } - if (mActivities.get(token) == null) { - // Only apply the override to application for activity token because the appearance of - // activity is usually more sensitive to the application resources. - return; + if (overrideConfig == null) { + final ActivityClientRecord r = mActivities.get(token); + if (r == null) { + // It is not an activity token. Nothing to do for application. + return; + } + overrideConfig = r.overrideConfig; + } + if (appConfigOverride != null) { + appConfigOverride[0] = overrideConfig; } // Apply the last override to application resources for compatibility. Because the Resources @@ -3503,7 +3542,8 @@ public final class ActivityThread extends ClientTransactionHandler { // The rotation adjustments must be applied before creating the activity, so the activity // can get the adjusted display info during creation. if (r.mPendingFixedRotationAdjustments != null) { - handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments); + handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments, + r.overrideConfig); r.mPendingFixedRotationAdjustments = null; } @@ -6777,7 +6817,11 @@ public final class ActivityThread extends ClientTransactionHandler { throw ex.rethrowFromSystemServer(); } if (holder == null) { - Slog.e(TAG, "Failed to find provider info for " + auth); + if (UserManager.get(c).isUserUnlocked(userId)) { + Slog.e(TAG, "Failed to find provider info for " + auth); + } else { + Slog.w(TAG, "Failed to find provider info for " + auth + " (user not unlocked)"); + } return null; } @@ -7388,6 +7432,10 @@ public final class ActivityThread extends ClientTransactionHandler { } } + public Bundle getCoreSettings() { + return mCoreSettings; + } + public int getIntCoreSetting(String key, int defaultValue) { synchronized (mResourcesManager) { if (mCoreSettings != null) { @@ -7397,6 +7445,18 @@ public final class ActivityThread extends ClientTransactionHandler { } } + /** + * Get the string value of the given key from core settings. + */ + public String getStringCoreSetting(String key, String defaultValue) { + synchronized (mResourcesManager) { + if (mCoreSettings != null) { + return mCoreSettings.getString(key, defaultValue); + } + return defaultValue; + } + } + float getFloatCoreSetting(String key, float defaultValue) { synchronized (mResourcesManager) { if (mCoreSettings != null) { diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 635ed138cc65ece9dfbf0af5556f3f12ea273f50..98a23f2b007502ce78dbac5bc714bd96c4993574 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -33,7 +33,10 @@ import android.hardware.display.VirtualDisplay; import android.os.Bundle; import android.os.UserHandle; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.util.Log; +import android.view.Display; +import android.view.DisplayInfo; import android.view.IWindow; import android.view.IWindowManager; import android.view.KeyEvent; @@ -102,6 +105,14 @@ public class ActivityView extends ViewGroup implements android.window.TaskEmbedd public ActivityView( @NonNull Context context, @NonNull AttributeSet attrs, int defStyle, boolean singleTaskInstance, boolean usePublicVirtualDisplay) { + this(context, attrs, defStyle, singleTaskInstance, usePublicVirtualDisplay, false); + } + + /** @hide */ + public ActivityView( + @NonNull Context context, @NonNull AttributeSet attrs, int defStyle, + boolean singleTaskInstance, boolean usePublicVirtualDisplay, + boolean disableSurfaceViewBackgroundLayer) { super(context, attrs, defStyle); if (useTaskOrganizer()) { mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this); @@ -109,7 +120,7 @@ public class ActivityView extends ViewGroup implements android.window.TaskEmbedd mTaskEmbedder = new VirtualDisplayTaskEmbedder(context, this, singleTaskInstance, usePublicVirtualDisplay); } - mSurfaceView = new SurfaceView(context); + mSurfaceView = new SurfaceView(context, null, 0, 0, disableSurfaceViewBackgroundLayer); // Since ActivityView#getAlpha has been overridden, we should use parent class's alpha // as master to synchronize surface view's alpha value. mSurfaceView.setAlpha(super.getAlpha()); @@ -352,18 +363,9 @@ public class ActivityView extends ViewGroup implements android.window.TaskEmbedd } /** - * Release this container. Activity launching will no longer be permitted. - *

Note: Calling this method is allowed after - * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before - * {@link StateCallback#onActivityViewDestroyed(ActivityView)}. - * - * @see StateCallback + * Release this container if it is initialized. Activity launching will no longer be permitted. */ public void release() { - if (!mTaskEmbedder.isInitialized()) { - throw new IllegalStateException( - "Trying to release container that is not initialized."); - } performRelease(); } @@ -408,6 +410,9 @@ public class ActivityView extends ViewGroup implements android.window.TaskEmbedd } private class SurfaceCallback implements SurfaceHolder.Callback { + private final DisplayInfo mTempDisplayInfo = new DisplayInfo(); + private final DisplayMetrics mTempMetrics = new DisplayMetrics(); + @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { if (!mTaskEmbedder.isInitialized()) { @@ -416,13 +421,21 @@ public class ActivityView extends ViewGroup implements android.window.TaskEmbedd mTmpTransaction.reparent(mTaskEmbedder.getSurfaceControl(), mSurfaceView.getSurfaceControl()).apply(); } + mTaskEmbedder.resizeTask(getWidth(), getHeight()); mTaskEmbedder.start(); } @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { - mTaskEmbedder.resizeTask(width, height); - mTaskEmbedder.notifyBoundsChanged(); + final Display display = getVirtualDisplay().getDisplay(); + if (!display.getDisplayInfo(mTempDisplayInfo)) { + return; + } + mTempDisplayInfo.getAppMetrics(mTempMetrics); + if (width != mTempMetrics.widthPixels || height != mTempMetrics.heightPixels) { + mTaskEmbedder.resizeTask(width, height); + mTaskEmbedder.notifyBoundsChanged(); + } } @Override @@ -479,7 +492,9 @@ public class ActivityView extends ViewGroup implements android.window.TaskEmbedd return; } mSurfaceView.getHolder().removeCallback(mSurfaceCallback); - mTaskEmbedder.release(); + if (mTaskEmbedder.isInitialized()) { + mTaskEmbedder.release(); + } mTaskEmbedder.setListener(null); mGuard.close(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b058dcd714dd68f72ac41513c713eab7cc72b78f..0a6827cde3d351e49812e78dff6b3029aecc0074 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -182,6 +182,8 @@ public class AppOpsManager { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) public static final long CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE = 148180766L; + private static final int MAX_UNFORWARDED_OPS = 10; + final Context mContext; @UnsupportedAppUsage @@ -216,6 +218,17 @@ public class AppOpsManager { @GuardedBy("sLock") private static @Nullable OnOpNotedCallback sOnOpNotedCallback; + /** + * Sync note-ops collected from {@link #readAndLogNotedAppops(Parcel)} that have not been + * delivered to a callback yet. + * + * Similar to {@link com.android.server.appop.AppOpsService#mUnforwardedAsyncNotedOps} for + * {@link COLLECT_ASYNC}. Used in situation when AppOpsManager asks to collect stacktrace with + * {@link #sMessageCollector}, which forces {@link COLLECT_SYNC} mode. + */ + @GuardedBy("sLock") + private static ArrayList sUnforwardedOps = new ArrayList<>(); + /** * Additional collector that collect accesses and forwards a few of them them via * {@link IAppOpsService#reportRuntimeAppOpAccessMessageAndGetConfig}. @@ -248,8 +261,9 @@ public class AppOpsManager { < SystemClock.elapsedRealtime()) { String stackTrace = getFormattedStackTrace(); try { + String packageName = ActivityThread.currentOpPackageName(); sConfig = getService().reportRuntimeAppOpAccessMessageAndGetConfig( - ActivityThread.currentOpPackageName(), op, stackTrace); + packageName == null ? "" : packageName, op, stackTrace); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -1107,9 +1121,12 @@ public class AppOpsManager { public static final int OP_AUTO_REVOKE_MANAGED_BY_INSTALLER = AppProtoEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER; + /** @hide */ + public static final int OP_NO_ISOLATED_STORAGE = AppProtoEnums.APP_OP_NO_ISOLATED_STORAGE; + /** @hide */ @UnsupportedAppUsage - public static final int _NUM_OP = 99; + public static final int _NUM_OP = 100; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1420,6 +1437,12 @@ public class AppOpsManager { @SystemApi public static final String OPSTR_LOADER_USAGE_STATS = "android:loader_usage_stats"; + /** + * AppOp granted to apps that we are started via {@code am instrument -e --no-isolated-storage} + * + * @hide + */ + public static final String OPSTR_NO_ISOLATED_STORAGE = "android:no_isolated_storage"; /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; @@ -1609,6 +1632,7 @@ public class AppOpsManager { OP_DEPRECATED_1, // deprecated OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, //AUTO_REVOKE_PERMISSIONS_IF_UNUSED OP_AUTO_REVOKE_MANAGED_BY_INSTALLER, //OP_AUTO_REVOKE_MANAGED_BY_INSTALLER + OP_NO_ISOLATED_STORAGE, // NO_ISOLATED_STORAGE }; /** @@ -1714,6 +1738,7 @@ public class AppOpsManager { "", // deprecated OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER, + OPSTR_NO_ISOLATED_STORAGE, }; /** @@ -1820,6 +1845,7 @@ public class AppOpsManager { "deprecated", "AUTO_REVOKE_PERMISSIONS_IF_UNUSED", "AUTO_REVOKE_MANAGED_BY_INSTALLER", + "NO_ISOLATED_STORAGE", }; /** @@ -1927,6 +1953,7 @@ public class AppOpsManager { null, // deprecated operation null, // no permission for OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED null, // no permission for OP_AUTO_REVOKE_MANAGED_BY_INSTALLER + null, // no permission for OP_NO_ISOLATED_STORAGE }; /** @@ -2034,6 +2061,7 @@ public class AppOpsManager { null, // deprecated operation null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED null, // AUTO_REVOKE_MANAGED_BY_INSTALLER + null, // NO_ISOLATED_STORAGE }; /** @@ -2140,6 +2168,7 @@ public class AppOpsManager { null, // deprecated operation null, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED null, // AUTO_REVOKE_MANAGED_BY_INSTALLER + null, // NO_ISOLATED_STORAGE }; /** @@ -2245,6 +2274,7 @@ public class AppOpsManager { AppOpsManager.MODE_IGNORED, // deprecated operation AppOpsManager.MODE_DEFAULT, // OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED AppOpsManager.MODE_ALLOWED, // OP_AUTO_REVOKE_MANAGED_BY_INSTALLER + AppOpsManager.MODE_ERRORED, // OP_NO_ISOLATED_STORAGE }; /** @@ -2354,6 +2384,7 @@ public class AppOpsManager { false, // deprecated operation false, // AUTO_REVOKE_PERMISSIONS_IF_UNUSED false, // AUTO_REVOKE_MANAGED_BY_INSTALLER + true, // NO_ISOLATED_STORAGE }; /** @@ -7339,15 +7370,17 @@ public class AppOpsManager { try { collectNoteOpCallsForValidation(op); int collectionMode = getNotedOpCollectionMode(uid, packageName, op); + boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID ? true : false; if (collectionMode == COLLECT_ASYNC) { if (message == null) { // Set stack trace as default message message = getFormattedStackTrace(); + shouldCollectMessage = true; } } int mode = mService.noteOperation(op, uid, packageName, attributionTag, - collectionMode == COLLECT_ASYNC, message); + collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); if (mode == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { @@ -7500,16 +7533,19 @@ public class AppOpsManager { try { collectNoteOpCallsForValidation(op); int collectionMode = getNotedOpCollectionMode(proxiedUid, proxiedPackageName, op); + boolean shouldCollectMessage = myUid == Process.SYSTEM_UID ? true : false; if (collectionMode == COLLECT_ASYNC) { if (message == null) { // Set stack trace as default message message = getFormattedStackTrace(); + shouldCollectMessage = true; } } int mode = mService.noteProxyOperation(op, proxiedUid, proxiedPackageName, proxiedAttributionTag, myUid, mContext.getOpPackageName(), - mContext.getAttributionTag(), collectionMode == COLLECT_ASYNC, message); + mContext.getAttributionTag(), collectionMode == COLLECT_ASYNC, message, + shouldCollectMessage); if (mode == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { @@ -7824,15 +7860,18 @@ public class AppOpsManager { try { collectNoteOpCallsForValidation(op); int collectionMode = getNotedOpCollectionMode(uid, packageName, op); + boolean shouldCollectMessage = Process.myUid() == Process.SYSTEM_UID ? true : false; if (collectionMode == COLLECT_ASYNC) { if (message == null) { // Set stack trace as default message message = getFormattedStackTrace(); + shouldCollectMessage = true; } } int mode = mService.startOperation(getClientId(), op, uid, packageName, - attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message); + attributionTag, startIfModeDefault, collectionMode == COLLECT_ASYNC, message, + shouldCollectMessage); if (mode == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { @@ -8163,6 +8202,14 @@ public class AppOpsManager { code = notedAppOps.nextSetBit(code + 1)) { if (sOnOpNotedCallback != null) { sOnOpNotedCallback.onNoted(new SyncNotedAppOp(code, attributionTag)); + } else { + String message = getFormattedStackTrace(); + sUnforwardedOps.add( + new AsyncNotedAppOp(code, Process.myUid(), attributionTag, + message, System.currentTimeMillis())); + if (sUnforwardedOps.size() > MAX_UNFORWARDED_OPS) { + sUnforwardedOps.remove(0); + } } } } @@ -8229,6 +8276,17 @@ public class AppOpsManager { } } } + synchronized (this) { + int numMissedSyncOps = sUnforwardedOps.size(); + for (int i = 0; i < numMissedSyncOps; i++) { + final AsyncNotedAppOp syncNotedAppOp = sUnforwardedOps.get(i); + if (sOnOpNotedCallback != null) { + sOnOpNotedCallback.getAsyncNotedExecutor().execute( + () -> sOnOpNotedCallback.onAsyncNoted(syncNotedAppOp)); + } + } + sUnforwardedOps.clear(); + } } } } @@ -8576,6 +8634,25 @@ public class AppOpsManager { } } + /** + * Reboots the ops history. + * + * @param offlineDurationMillis The duration to wait between + * tearing down and initializing the history. Must be greater + * than or equal to zero. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_APPOPS) + public void rebootHistory(long offlineDurationMillis) { + try { + mService.rebootHistory(offlineDurationMillis); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Pulls current AppOps access report and picks package and op to watch for next access report * Returns null if no reports were collected since last call. There is no guarantee of report diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java index 309e91f1e4ffca19e2f1fcc11318cc8c63d90a8d..5e032f00a3a02439ea07ea90f79fe48862dd3154 100644 --- a/core/java/android/app/AppOpsManagerInternal.java +++ b/core/java/android/app/AppOpsManagerInternal.java @@ -22,7 +22,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.app.IAppOpsCallback; -import com.android.internal.util.function.HexFunction; +import com.android.internal.util.function.HeptFunction; import com.android.internal.util.function.QuadFunction; /** @@ -73,9 +73,9 @@ public abstract class AppOpsManagerInternal { */ int noteOperation(int code, int uid, @Nullable String packageName, @Nullable String featureId, boolean shouldCollectAsyncNotedOp, - @Nullable String message, - @NonNull HexFunction - superImpl); + @Nullable String message, boolean shouldCollectMessage, + @NonNull HeptFunction superImpl); } /** diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index cfe0aff05d4ab5bd251dccb6bf628bc952fc0386..e7b3e14bfda72e9c8efe6287af806c065f079200 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -502,7 +502,7 @@ public final class ApplicationExitInfo implements Parcelable { * Return the defining kernel user identifier, maybe different from {@link #getRealUid} and * {@link #getPackageUid}, if an external service has the * {@link android.R.styleable#AndroidManifestService_useAppZygote android:useAppZygote} set - * to true and was bound with the flag + * to true and was bound with the flag * {@link android.content.Context#BIND_EXTERNAL_SERVICE} - in this case, this field here will * be the kernel user identifier of the external service provider. */ diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 6bd8fd7c6ecfbdfa8308da49b1c9919c1225648d..c9031b711657b3adeeefef078b2ec3336340def6 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -139,6 +139,10 @@ public class ApplicationPackageManager extends PackageManager { public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS = "app_permission_button_allow_always"; + // Name of the package which the permission controller's resources are in. + public static final String PERMISSION_CONTROLLER_RESOURCE_PACKAGE = + "com.android.permissioncontroller"; + private final Object mLock = new Object(); @GuardedBy("mLock") @@ -759,25 +763,26 @@ public class ApplicationPackageManager extends PackageManager { @Override public void revokeRuntimePermission(String packageName, String permName, UserHandle user) { + revokeRuntimePermission(packageName, permName, user, null); + } + + @Override + public void revokeRuntimePermission(String packageName, String permName, UserHandle user, + String reason) { if (DEBUG_TRACE_PERMISSION_UPDATES && shouldTraceGrant(packageName, permName, user.getIdentifier())) { Log.i(TAG, "App " + mContext.getPackageName() + " is revoking " + packageName + " " - + permName + " for user " + user.getIdentifier(), new RuntimeException()); + + permName + " for user " + user.getIdentifier() + " with reason " + reason, + new RuntimeException()); } try { mPermissionManager - .revokeRuntimePermission(packageName, permName, user.getIdentifier()); + .revokeRuntimePermission(packageName, permName, user.getIdentifier(), reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - @Override - public void revokeRuntimePermission(String packageName, String permName, UserHandle user, - String reason) { - // TODO evanseverson: impl - } - @Override public int getPermissionFlags(String permName, String packageName, UserHandle user) { try { @@ -893,8 +898,7 @@ public class ApplicationPackageManager extends PackageManager { mContext.createPackageContext(permissionController, 0); int textId = context.getResources().getIdentifier(APP_PERMISSION_BUTTON_ALLOW_ALWAYS, - "string", "com.android.permissioncontroller"); -// permissionController); STOPSHIP b/147434671 + "string", PERMISSION_CONTROLLER_RESOURCE_PACKAGE); if (textId != 0) { return context.getText(textId); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 48160e475d504fd329a43984fbabe5f08d1de4e8..505b498e3cf6fdfb65c9e74d11a36cb894cb97d5 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1087,7 +1087,7 @@ class ContextImpl extends Context { try { String resolvedType = null; if (fillInIntent != null) { - fillInIntent.migrateExtraStreamToClipData(); + fillInIntent.migrateExtraStreamToClipData(this); fillInIntent.prepareToLeaveProcess(this); resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver()); } @@ -1900,24 +1900,31 @@ class ContextImpl extends Context { @Override public Object getSystemService(String name) { - // Check incorrect Context usage. - if (isUiComponent(name) && !isUiContext() && vmIncorrectContextUseEnabled()) { - final String errorMessage = "Tried to access visual service " - + SystemServiceRegistry.getSystemServiceClassName(name) - + " from a non-visual Context. "; - final String message = "Visual services, such as WindowManager, WallpaperService or " - + "LayoutInflater should be accessed from Activity or other visual Context. " - + "Use an Activity or a Context created with " - + "Context#createWindowContext(int, Bundle), which are adjusted to the " - + "configuration and visual bounds of an area on screen."; - final Exception exception = new IllegalAccessException(errorMessage); - StrictMode.onIncorrectContextUsed(message, exception); - Log.e(TAG, errorMessage + message, exception); + if (vmIncorrectContextUseEnabled()) { + // We may override this API from outer context. + final boolean isUiContext = isUiContext() || isOuterUiContext(); + // Check incorrect Context usage. + if (isUiComponent(name) && !isUiContext) { + final String errorMessage = "Tried to access visual service " + + SystemServiceRegistry.getSystemServiceClassName(name) + + " from a non-visual Context:" + getOuterContext(); + final String message = "Visual services, such as WindowManager, WallpaperService " + + "or LayoutInflater should be accessed from Activity or other visual " + + "Context. Use an Activity or a Context created with " + + "Context#createWindowContext(int, Bundle), which are adjusted to " + + "the configuration and visual bounds of an area on screen."; + final Exception exception = new IllegalAccessException(errorMessage); + StrictMode.onIncorrectContextUsed(message, exception); + Log.e(TAG, errorMessage + " " + message, exception); + } } - return SystemServiceRegistry.getSystemService(this, name); } + private boolean isOuterUiContext() { + return getOuterContext() != null && getOuterContext().isUiContext(); + } + @Override public String getSystemServiceName(Class serviceClass) { return SystemServiceRegistry.getSystemServiceName(serviceClass); @@ -2369,6 +2376,7 @@ class ContextImpl extends Context { context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId, overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(), mResources.getLoaders())); + context.mIsUiContext = isUiContext() || isOuterUiContext(); return context; } diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index 1278ff6817fd6131947b22357328a8dfc85171f3..0719422632d1d89a946a213c47c3a3b5945a953c 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -25,6 +25,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ClipDescription; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentUris; @@ -50,11 +51,13 @@ import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.util.LongSparseArray; import android.util.Pair; +import android.webkit.MimeTypeMap; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * The download manager is a system service that handles long-running HTTP downloads. Clients may @@ -1554,6 +1557,7 @@ public class DownloadManager { values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD); values.put(Downloads.Impl._DATA, path); + values.put(Downloads.Impl.COLUMN_MIME_TYPE, resolveMimeType(new File(path))); values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS); values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length); values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, @@ -1569,6 +1573,58 @@ public class DownloadManager { return Long.parseLong(downloadUri.getLastPathSegment()); } + /** + * Shamelessly borrowed from + * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/MimeUtils.java} + * + * @hide + */ + private static @NonNull String resolveMimeType(@NonNull File file) { + final String extension = extractFileExtension(file.getPath()); + if (extension == null) return ClipDescription.MIMETYPE_UNKNOWN; + + final String mimeType = MimeTypeMap.getSingleton() + .getMimeTypeFromExtension(extension.toLowerCase(Locale.ROOT)); + if (mimeType == null) return ClipDescription.MIMETYPE_UNKNOWN; + + return mimeType; + } + + /** + * Shamelessly borrowed from + * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/FileUtils.java} + * + * @hide + */ + private static @Nullable String extractDisplayName(@Nullable String data) { + if (data == null) return null; + if (data.indexOf('/') == -1) { + return data; + } + if (data.endsWith("/")) { + data = data.substring(0, data.length() - 1); + } + return data.substring(data.lastIndexOf('/') + 1); + } + + /** + * Shamelessly borrowed from + * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/FileUtils.java} + * + * @hide + */ + private static @Nullable String extractFileExtension(@Nullable String data) { + if (data == null) return null; + data = extractDisplayName(data); + + final int lastDot = data.lastIndexOf('.'); + if (lastDot == -1) { + return null; + } else { + return data.substring(lastDot + 1); + } + } + private static final String NON_DOWNLOADMANAGER_DOWNLOAD = "non-dwnldmngr-download-dont-retry2download"; diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index fd3eb0618429cce66cc1fe8a609507e6f870b26c..68824cd26eaaf061a9485cad3d9f14e6bbfffd68 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -434,8 +434,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { mSharedElementNotified = true; delayCancel(); - if (!mActivity.isTopOfTask() || (mIsReturning && !mActivity.isTaskRoot() - && !mSharedElements.isEmpty())) { + if (!mActivity.isTopOfTask()) { mResultReceiver.send(MSG_ALLOW_RETURN_TRANSITION, null); } diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index c6a0de458df0ee88a68f48ff4a7f44c24eb0bed9..e4e5ba37ddbf979bff73cbd44081e203ba3e9b2b 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -1179,6 +1179,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * the signature of the app declaring the permissions. *

*

+ * Call {@link #shouldShowRequestPermissionRationale(String)} before calling this API + * to check if the system recommends to show a rationale UI before asking for a permission. + *

+ *

* If your app does not have the requested permissions the user will be presented * with UI for accepting them. After the user has accepted or rejected the * requested permissions you will receive a callback on {@link @@ -1213,29 +1217,6 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * true because in this case the activity would not receive * result callbacks including {@link #onRequestPermissionsResult(int, String[], int[])}. *

- *

- * A sample permissions request looks like this: - *

- *

- * private void showContacts() { - * if (getActivity().checkSelfPermission(Manifest.permission.READ_CONTACTS) - * != PackageManager.PERMISSION_GRANTED) { - * requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, - * PERMISSIONS_REQUEST_READ_CONTACTS); - * } else { - * doShowContacts(); - * } - * } - * - * {@literal @}Override - * public void onRequestPermissionsResult(int requestCode, String[] permissions, - * int[] grantResults) { - * if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS - * && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - * doShowContacts(); - * } - * } - *

* * @param permissions The requested permissions. Must me non-null and not empty. * @param requestCode Application specific request code to match with a result @@ -1275,20 +1256,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } /** - * Gets whether you should show UI with rationale for requesting a permission. - * You should do this only if you do not have the permission and the context in - * which the permission is requested does not clearly communicate to the user - * what would be the benefit from granting this permission. - *

- * For example, if you write a camera app, requesting the camera permission - * would be expected by the user and no rationale for why it is requested is - * needed. If however, the app needs location for tagging photos then a non-tech - * savvy user may wonder how location is related to taking photos. In this case - * you may choose to show UI with rationale of requesting this permission. - *

+ * Gets whether you should show UI with rationale before requesting a permission. * * @param permission A permission your app wants to request. - * @return Whether you can show permission rationale UI. + * @return Whether you should show permission rationale UI. * * @see Context#checkSelfPermission(String) * @see #requestPermissions(String[], int) diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index e84c5e574713efe2ccce187b8bf055a720b5d2bc..945957738f8eb30ba9a172caa4a3fa89b226527f 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -677,4 +677,10 @@ interface IActivityManager { * Return whether the app freezer is supported (true) or not (false) by this system. */ boolean isAppFreezerSupported(); + + + /** + * Kills uid with the reason of permission change. + */ + void killUidForPermissionChange(int appId, int userId, String reason); } diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index 2d06ee8d06bcc6ad16ab6db602c5028f99766602..aec9f3e98960bcb3a13c18301b4a50950b8b3cc0 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -216,4 +216,16 @@ oneway interface ITaskStackListener { * in {@link android.content.pm.ActivityInfo}. */ void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation); + + /** + * Called when a rotation is about to start on the foreground activity. + * This applies for: + * * free sensor rotation + * * forced rotation + * * rotation settings set through adb command line + * * rotation that occurs when rotation tile is toggled in quick settings + * + * @param displayId id of the display where activity will rotate + */ + void onActivityRotation(int displayId); } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index e233adeb3f65a69bdadfb5aaa13bbe2097f5eda8..721525d9af9d7b7466e0111a910d1feb956f0ed7 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1718,7 +1718,7 @@ public class Instrumentation { } } try { - intent.migrateExtraStreamToClipData(); + intent.migrateExtraStreamToClipData(who); intent.prepareToLeaveProcess(who); int result = ActivityTaskManager.getService().startActivity(whoThread, who.getBasePackageName(), who.getAttributionTag(), intent, @@ -1788,7 +1788,7 @@ public class Instrumentation { try { String[] resolvedTypes = new String[intents.length]; for (int i=0; i dataOnlyInputs = new ArrayList<>(); - RemoteInput[] previousDataInputs = - (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS); + RemoteInput[] previousDataInputs = getParcelableArrayFromBundle( + mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); if (previousDataInputs != null) { for (RemoteInput input : previousDataInputs) { dataOnlyInputs.add(input); @@ -5368,8 +5368,8 @@ public class Notification implements Parcelable big.setViewVisibility(R.id.actions_container, View.GONE); } - RemoteInputHistoryItem[] replyText = (RemoteInputHistoryItem[]) - mN.extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle( + mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class); if (validRemoteInput && replyText != null && replyText.length > 0 && !TextUtils.isEmpty(replyText[0].getText()) && p.maxRemoteInputHistory > 0) { @@ -7495,6 +7495,7 @@ public class Notification implements Parcelable mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); + mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON); } /** @@ -7625,9 +7626,7 @@ public class Notification implements Parcelable } boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT; - Icon largeIcon = isConversationLayout && mShortcutIcon != null - ? mShortcutIcon - : mBuilder.mN.mLargeIcon; + Icon largeIcon = mBuilder.mN.mLargeIcon; TemplateBindResult bindResult = new TemplateBindResult(); StandardTemplateParams p = mBuilder.mParams.reset() .hasProgress(false) @@ -7671,6 +7670,8 @@ public class Notification implements Parcelable contentView.setCharSequence(R.id.status_bar_latest_event_content, "setConversationTitle", conversationTitle); if (isConversationLayout) { + contentView.setIcon(R.id.status_bar_latest_event_content, + "setShortcutIcon", mShortcutIcon); contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsImportantConversation", isImportantConversation); } @@ -8154,8 +8155,9 @@ public class Notification implements Parcelable if (mBuilder.mActions.size() > 0) { maxRows--; } - RemoteInputHistoryItem[] remoteInputHistory = (RemoteInputHistoryItem[]) - mBuilder.mN.extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle( + mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + RemoteInputHistoryItem.class); if (remoteInputHistory != null && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) { // Let's remove some messages to make room for the remote input history. @@ -9578,8 +9580,8 @@ public class Notification implements Parcelable mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); - Notification[] pages = getNotificationArrayFromBundle( - wearableBundle, KEY_PAGES); + Notification[] pages = getParcelableArrayFromBundle( + wearableBundle, KEY_PAGES, Notification.class); if (pages != null) { Collections.addAll(mPages, pages); } @@ -10837,17 +10839,22 @@ public class Notification implements Parcelable } /** - * Get an array of Notification objects from a parcelable array bundle field. + * Get an array of Parcelable objects from a parcelable array bundle field. * Update the bundle to have a typed array so fetches in the future don't need * to do an array copy. */ - private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) { - Parcelable[] array = bundle.getParcelableArray(key); - if (array instanceof Notification[] || array == null) { - return (Notification[]) array; + @Nullable + private static T[] getParcelableArrayFromBundle( + Bundle bundle, String key, Class itemClass) { + final Parcelable[] array = bundle.getParcelableArray(key); + final Class arrayClass = Array.newInstance(itemClass, 0).getClass(); + if (arrayClass.isInstance(array) || array == null) { + return (T[]) array; + } + final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length); + for (int i = 0; i < array.length; i++) { + typedArray[i] = (T) array[i]; } - Notification[] typedArray = Arrays.copyOf(array, array.length, - Notification[].class); bundle.putParcelableArray(key, typedArray); return typedArray; } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 792f840638a9db4d761078e9ad745e54ff41044b..cd352e141994388e16dbb69d6ac3487a16e3241a 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -351,7 +351,7 @@ public final class PendingIntent implements Parcelable { String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; try { - intent.migrateExtraStreamToClipData(); + intent.migrateExtraStreamToClipData(context); intent.prepareToLeaveProcess(context); IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature( @@ -377,7 +377,7 @@ public final class PendingIntent implements Parcelable { String resolvedType = intent != null ? intent.resolveTypeIfNeeded( context.getContentResolver()) : null; try { - intent.migrateExtraStreamToClipData(); + intent.migrateExtraStreamToClipData(context); intent.prepareToLeaveProcess(context); IIntentSender target = ActivityManager.getService().getIntentSenderWithFeature( @@ -491,7 +491,7 @@ public final class PendingIntent implements Parcelable { String packageName = context.getPackageName(); String[] resolvedTypes = new String[intents.length]; for (int i=0; i> sEmptyReferencePredicate = - weakRef -> weakRef == null || weakRef.get() == null; - /** * The global compatibility settings. */ @@ -100,6 +97,7 @@ public class ResourcesManager { */ @UnsupportedAppUsage private final ArrayList> mResourceReferences = new ArrayList<>(); + private final ReferenceQueue mResourcesReferencesQueue = new ReferenceQueue<>(); private static class ApkKey { public final String path; @@ -155,6 +153,7 @@ public class ResourcesManager { } public final Configuration overrideConfig = new Configuration(); public final ArrayList> activityResources = new ArrayList<>(); + final ReferenceQueue activityResourcesQueue = new ReferenceQueue<>(); } /** @@ -667,12 +666,15 @@ public class ResourcesManager { @NonNull CompatibilityInfo compatInfo) { final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( activityToken); + cleanupReferences(activityResources.activityResources, + activityResources.activityResourcesQueue); Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) : new Resources(classLoader); resources.setImpl(impl); resources.setCallbacks(mUpdateCallbacks); - activityResources.activityResources.add(new WeakReference<>(resources)); + activityResources.activityResources.add( + new WeakReference<>(resources, activityResources.activityResourcesQueue)); if (DEBUG) { Slog.d(TAG, "- creating new ref=" + resources); Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); @@ -682,11 +684,13 @@ public class ResourcesManager { private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) { + cleanupReferences(mResourceReferences, mResourcesReferencesQueue); + Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) : new Resources(classLoader); resources.setImpl(impl); resources.setCallbacks(mUpdateCallbacks); - mResourceReferences.add(new WeakReference<>(resources)); + mResourceReferences.add(new WeakReference<>(resources, mResourcesReferencesQueue)); if (DEBUG) { Slog.d(TAG, "- creating new ref=" + resources); Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl); @@ -752,7 +756,6 @@ public class ResourcesManager { updateResourcesForActivity(token, overrideConfig, displayId, false /* movedToDifferentDisplay */); - cleanupReferences(token); rebaseKeyForActivity(token, key); synchronized (this) { @@ -778,10 +781,6 @@ public class ResourcesManager { final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(activityToken); - // Clean up any dead references so they don't pile up. - ArrayUtils.unstableRemoveIf(activityResources.activityResources, - sEmptyReferencePredicate); - // Rebase the key's override config on top of the Activity's base override. if (key.hasOverrideConfiguration() && !activityResources.overrideConfig.equals(Configuration.EMPTY)) { @@ -794,21 +793,21 @@ public class ResourcesManager { /** * Check WeakReferences and remove any dead references so they don't pile up. - * @param activityToken optional token to clean up Activity resources */ - private void cleanupReferences(IBinder activityToken) { - synchronized (this) { - if (activityToken != null) { - ActivityResources activityResources = mActivityResourceReferences.get( - activityToken); - if (activityResources != null) { - ArrayUtils.unstableRemoveIf(activityResources.activityResources, - sEmptyReferencePredicate); - } - } else { - ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate); - } + private static void cleanupReferences(ArrayList> references, + ReferenceQueue referenceQueue) { + Reference enduedRef = referenceQueue.poll(); + if (enduedRef == null) { + return; } + + final HashSet> deadReferences = new HashSet<>(); + for (; enduedRef != null; enduedRef = referenceQueue.poll()) { + deadReferences.add(enduedRef); + } + + ArrayUtils.unstableRemoveIf(references, + (ref) -> ref == null || deadReferences.contains(ref)); } /** @@ -896,8 +895,6 @@ public class ResourcesManager { loaders == null ? null : loaders.toArray(new ResourcesLoader[0])); classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader(); - cleanupReferences(activityToken); - if (activityToken != null) { rebaseKeyForActivity(activityToken, key); } diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 8ad33dbf9f4a9c520018f2a9efaf88aa3c5aecc7..b65ae7a0a7b94f18ffeb8393966d096d63477f9f 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -27,6 +27,15 @@ } ] }, + { + "file_patterns": ["(/|^)AppOpsManager.java"], + "name": "CtsStatsdHostTestCases", + "options": [ + { + "include-filter": "android.cts.statsd.atom.UidAtomTests#testAppOps" + } + ] + }, { "file_patterns": ["(/|^)AppOpsManager.java"], "name": "CtsPermission2TestCases", diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index c7a2a1e11c9eb3c4dcaad7f0a4378e426435c844..f3f00e50715b31b4014776df8958dd4c43ace293 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -16,8 +16,6 @@ package android.app; -import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; @@ -170,6 +168,14 @@ public class TaskInfo { @Nullable public ActivityInfo topActivityInfo; + /** + * 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. + * @hide + */ + public boolean isResizeable; + TaskInfo() { // Do nothing } @@ -192,11 +198,6 @@ public class TaskInfo { } } - /** @hide */ - public boolean isResizable() { - return resizeMode != RESIZE_MODE_UNRESIZEABLE; - } - /** @hide */ @NonNull @TestApi @@ -245,6 +246,7 @@ public class TaskInfo { topActivityInfo = source.readInt() != 0 ? ActivityInfo.CREATOR.createFromParcel(source) : null; + isResizeable = source.readBoolean(); } /** @@ -294,6 +296,7 @@ public class TaskInfo { dest.writeInt(1); topActivityInfo.writeToParcel(dest, flags); } + dest.writeBoolean(isResizeable); } @Override @@ -308,6 +311,7 @@ public class TaskInfo { + " lastActiveTime=" + lastActiveTime + " supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow + " resizeMode=" + resizeMode + + " isResizeable=" + isResizeable + " token=" + token + " topActivityType=" + topActivityType + " pictureInPictureParams=" + pictureInPictureParams diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java index 5d8daf88a8de6c6e134d1655511bd4e1386d2402..f137d6858a689bec88d7b7c8723780061ac71095 100644 --- a/core/java/android/app/TaskStackListener.java +++ b/core/java/android/app/TaskStackListener.java @@ -199,4 +199,8 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { @Override public void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation) { } + + @Override + public void onActivityRotation(int displayId) { + } } diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 82e988109db8e4db4eb55f42d927959ce7a5882c..ce51dba76780ce08f3f3d7c6b4b0f765f9197ed6 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -294,7 +294,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } final long identity = Binder.clearCallingIdentity(); try { - mPermissionManager.revokeRuntimePermission(packageName, permission, userId); + mPermissionManager.revokeRuntimePermission(packageName, permission, userId, null); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java index 4d9970c2c144f17a44ee26bf95974b4f365c38d8..15ff531b445d985573c1e4371845f33e17d6f272 100644 --- a/core/java/android/app/admin/DevicePolicyCache.java +++ b/core/java/android/app/admin/DevicePolicyCache.java @@ -41,7 +41,8 @@ public abstract class DevicePolicyCache { /** * See {@link DevicePolicyManager#getScreenCaptureDisabled} */ - public abstract boolean getScreenCaptureDisabled(@UserIdInt int userHandle); + public abstract boolean isScreenCaptureAllowed(@UserIdInt int userHandle, + boolean ownerCanAddInternalSystemWindow); /** * Caches {@link DevicePolicyManager#getPasswordQuality(android.content.ComponentName)} of the @@ -56,8 +57,9 @@ public abstract class DevicePolicyCache { private static final EmptyDevicePolicyCache INSTANCE = new EmptyDevicePolicyCache(); @Override - public boolean getScreenCaptureDisabled(int userHandle) { - return false; + public boolean isScreenCaptureAllowed(int userHandle, + boolean ownerCanAddInternalSystemWindow) { + return true; } @Override diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index c65064324c8cdd03890dc875b98550c08d27b03f..322cac81d58b3b5739b5ac616ef05983b082735a 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4247,6 +4247,12 @@ public class DevicePolicyManager { * device. After this method is called, the device must be unlocked using strong authentication * (PIN, pattern, or password). This API is intended for use only by device admins. *

+ * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have + * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is + * true, then the method will return without completing any action. Before version + * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature, + * regardless of the caller's permissions. + *

* The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} * to be able to call this method; if it has not, a security exception will be thrown. *

@@ -4274,6 +4280,12 @@ public class DevicePolicyManager { * device. After this method is called, the device must be unlocked using strong authentication * (PIN, pattern, or password). This API is intended for use only by device admins. *

+ * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have + * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is + * true, then the method will return without completing any action. Before version + * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature, + * regardless of the caller's permissions. + *

* The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} * to be able to call this method; if it has not, a security exception will be thrown. *

@@ -5868,12 +5880,22 @@ public class DevicePolicyManager { * returned by {@link #getParentProfileInstance(ComponentName)}, where the caller must be * the profile owner of an organization-owned managed profile. *

- * If the caller is device owner or called on the parent instance, then the - * restriction will be applied to all users. + * If the caller is device owner, then the restriction will be applied to all users. If + * called on the parent instance, then the restriction will be applied on the personal profile. *

* The calling device admin must have requested * {@link DeviceAdminInfo#USES_POLICY_DISABLE_CAMERA} to be able to call this method; if it has * not, a security exception will be thrown. + *

+ * Note, this policy type is deprecated for legacy device admins since + * {@link android.os.Build.VERSION_CODES#Q}. On Android + * {@link android.os.Build.VERSION_CODES#Q} devices, legacy device admins targeting SDK + * version {@link android.os.Build.VERSION_CODES#P} or below can still call this API to + * disable camera, while legacy device admins targeting SDK version + * {@link android.os.Build.VERSION_CODES#Q} will receive a SecurityException. Starting + * from Android {@link android.os.Build.VERSION_CODES#R}, requests to disable camera from + * legacy device admins targeting SDK version {@link android.os.Build.VERSION_CODES#P} or + * below will be silently ignored. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param disabled Whether or not the camera should be disabled. diff --git a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java index bea1bd6e70d6c76e16084431d04a86729d83f2d7..b3f9e31abaa444db1d990d805e8199487b46e780 100644 --- a/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java +++ b/core/java/android/app/contentsuggestions/ContentSuggestionsManager.java @@ -95,7 +95,7 @@ public final class ContentSuggestionsManager { try { mService.provideContextBitmap(mUser, bitmap, imageContextRequestExtras); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -117,7 +117,7 @@ public final class ContentSuggestionsManager { try { mService.provideContextImage(mUser, taskId, imageContextRequestExtras); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -146,7 +146,7 @@ public final class ContentSuggestionsManager { mService.suggestContentSelections( mUser, request, new SelectionsCallbackWrapper(callback, callbackExecutor)); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -173,7 +173,7 @@ public final class ContentSuggestionsManager { mService.classifyContentSelections( mUser, request, new ClassificationsCallbackWrapper(callback, callbackExecutor)); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -193,7 +193,7 @@ public final class ContentSuggestionsManager { try { mService.notifyInteraction(mUser, requestId, interaction); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -213,9 +213,10 @@ public final class ContentSuggestionsManager { mService.isEnabled(mUser, receiver); return receiver.getIntResult() != 0; } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get the enable status."); } - return false; } /** diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index d6e77624a967e8cdf8f13fb7d4eaf6a6cafe240d..fc8248e1012af07b12e9f2e893fb93cc3cb9292a 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -41,6 +41,7 @@ import android.os.Messenger; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.telephony.TelephonyManager; import android.util.DataUnit; import android.util.Log; @@ -198,6 +199,12 @@ public class NetworkStatsManager { * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} * etc. * @param subscriberId If applicable, the subscriber id of the network interface. + *

Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param startTime Start of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param endTime End of period. Defined in terms of "Unix time", see @@ -231,6 +238,12 @@ public class NetworkStatsManager { * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} * etc. * @param subscriberId If applicable, the subscriber id of the network interface. + *

Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param startTime Start of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param endTime End of period. Defined in terms of "Unix time", see @@ -268,6 +281,12 @@ public class NetworkStatsManager { * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} * etc. * @param subscriberId If applicable, the subscriber id of the network interface. + *

Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param startTime Start of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param endTime End of period. Defined in terms of "Unix time", see @@ -301,7 +320,7 @@ public class NetworkStatsManager { /** * Query network usage statistics details for a given uid. * - * #see queryDetailsForUidTagState(int, String, long, long, int, int, int) + * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int) */ public NetworkStats queryDetailsForUid(int networkType, String subscriberId, long startTime, long endTime, int uid) throws SecurityException { @@ -319,7 +338,7 @@ public class NetworkStatsManager { /** * Query network usage statistics details for a given uid and tag. * - * #see queryDetailsForUidTagState(int, String, long, long, int, int, int) + * @see #queryDetailsForUidTagState(int, String, long, long, int, int, int) */ public NetworkStats queryDetailsForUidTag(int networkType, String subscriberId, long startTime, long endTime, int uid, int tag) throws SecurityException { @@ -344,6 +363,12 @@ public class NetworkStatsManager { * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} * etc. * @param subscriberId If applicable, the subscriber id of the network interface. + *

Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param startTime Start of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param endTime End of period. Defined in terms of "Unix time", see @@ -398,6 +423,12 @@ public class NetworkStatsManager { * {@link ConnectivityManager#TYPE_MOBILE}, {@link ConnectivityManager#TYPE_WIFI} * etc. * @param subscriberId If applicable, the subscriber id of the network interface. + *

Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when querying for the mobile network type to receive usage + * for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param startTime Start of period. Defined in terms of "Unix time", see * {@link java.lang.System#currentTimeMillis}. * @param endTime End of period. Defined in terms of "Unix time", see @@ -455,7 +486,7 @@ public class NetworkStatsManager { /** * Registers to receive notifications about data usage on specified networks. * - * #see registerUsageCallback(int, String[], long, UsageCallback, Handler) + * @see #registerUsageCallback(int, String, long, UsageCallback, Handler) */ public void registerUsageCallback(int networkType, String subscriberId, long thresholdBytes, UsageCallback callback) { @@ -472,6 +503,12 @@ public class NetworkStatsManager { * @param networkType Type of network to monitor. Either {@link ConnectivityManager#TYPE_MOBILE} or {@link ConnectivityManager#TYPE_WIFI}. * @param subscriberId If applicable, the subscriber id of the network interface. + *

Starting with API level 29, the {@code subscriberId} is guarded by + * additional restrictions. Calling apps that do not meet the new + * requirements to access the {@code subscriberId} can provide a {@code + * null} value when registering for the mobile network type to receive + * notifications for all mobile networks. For additional details see {@link + * TelephonyManager#getSubscriberId()}. * @param thresholdBytes Threshold in bytes to be notified on. * @param callback The {@link UsageCallback} that the system will call when data usage * has exceeded the specified threshold. diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java index 0f999ad68a626dd2611e4269699c35e858b13acf..3522b1b8aff5756cadb165f3bdfa43d169aafa05 100644 --- a/core/java/android/app/usage/UsageEvents.java +++ b/core/java/android/app/usage/UsageEvents.java @@ -133,6 +133,7 @@ public final class UsageEvents implements Parcelable { /** * An event type denoting that a component was in the foreground when the stats * rolled-over. This is effectively treated as a {@link #ACTIVITY_PAUSED}. + * This event has a non-null packageName, and a null className. * {@hide} */ public static final int END_OF_DAY = 3; diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 2ce6a86753a6e449c54e877d7aa3ac94eb7059c2..8a6cc95319fbbca3352ad601679df91db4e43277 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -154,6 +154,7 @@ public final class UsageStatsManager { * been misbehaving in some manner. * Apps in this bucket will have the most restrictions, including network restrictions and * additional restrictions on jobs. + *

Note: this bucket is not enabled in {@link Build.VERSION_CODES#R}. * @see #getAppStandbyBucket() */ public static final int STANDBY_BUCKET_RESTRICTED = 45; diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java index 24be45cb20fe9397b48ce57a876fd098d30bc3c3..8e687413b7e1bb88d3f6403142b3ac5fff8ea135 100644 --- a/core/java/android/companion/BluetoothDeviceFilterUtils.java +++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java @@ -51,13 +51,6 @@ public class BluetoothDeviceFilterUtils { return s == null ? null : Pattern.compile(s); } - static boolean matches(ScanFilter filter, BluetoothDevice device) { - boolean result = matchesAddress(filter.getDeviceAddress(), device) - && matchesServiceUuid(filter.getServiceUuid(), filter.getServiceUuidMask(), device); - if (DEBUG) debugLogMatchResult(result, device, filter); - return result; - } - static boolean matchesAddress(String deviceAddress, BluetoothDevice device) { final boolean result = deviceAddress == null || (device != null && deviceAddress.equals(device.getAddress())); diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java index dccfb0346c9c2576dc06e6c0e85f54759027cc2b..8c071fe9910461a52a424883079204f3ea05db80 100644 --- a/core/java/android/companion/BluetoothLeDeviceFilter.java +++ b/core/java/android/companion/BluetoothLeDeviceFilter.java @@ -37,7 +37,6 @@ import android.util.Log; import com.android.internal.util.BitUtils; import com.android.internal.util.ObjectUtils; -import com.android.internal.util.Preconditions; import libcore.util.HexEncoding; @@ -166,21 +165,18 @@ public final class BluetoothLeDeviceFilter implements DeviceFilter { /** @hide */ @Override - public boolean matches(ScanResult device) { - boolean result = matches(device.getDevice()) + public boolean matches(ScanResult scanResult) { + BluetoothDevice device = scanResult.getDevice(); + boolean result = getScanFilter().matches(scanResult) + && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device) && (mRawDataFilter == null - || BitUtils.maskedEquals(device.getScanRecord().getBytes(), + || BitUtils.maskedEquals(scanResult.getScanRecord().getBytes(), mRawDataFilter, mRawDataFilterMask)); if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device + ") -> " + result); return result; } - private boolean matches(BluetoothDevice device) { - return BluetoothDeviceFilterUtils.matches(getScanFilter(), device) - && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device); - } - /** @hide */ @Override public int getMediumType() { diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 4bd7b059dfaac42b1552a9a015c8e35ab70a50b4..591a714bfb930ddbdab1b26a07a70ef850101683 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -65,6 +65,13 @@ public final class CompanionDeviceManager { /** * A device, returned in the activity result of the {@link IntentSender} received in * {@link Callback#onDeviceFound} + * + * Type is: + *

    + *
  • for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}
  • + *
  • for Bluetooth LE - {@link android.bluetooth.le.ScanResult}
  • + *
  • for WiFi - {@link android.net.wifi.ScanResult}
  • + *
*/ public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 5dc41e483640a81f9cb7c7d3303c76ef4e30c7fb..89abfc95d634c7ea18067ef0f4ba30683635329a 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -1151,6 +1151,9 @@ public class ContextWrapper extends Context { */ @Override public boolean isUiContext() { + if (mBase == null) { + return false; + } return mBase.isUiContext(); } } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index baaf8f76797a66dbf42e7c9f9285df7070803603..ededd0d2ea30e553fa3ffa0e76139025d794f000 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1884,6 +1884,9 @@ public class Intent implements Parcelable, Cloneable { /** * Activity action: Launch UI to manage auto-revoke state. + * + * This is equivalent to Intent#ACTION_APPLICATION_DETAILS_SETTINGS + * *

* Input: {@link Intent#setData data} should be a {@code package}-scheme {@link Uri} with * a package name, whose auto-revoke state will be reviewed (mandatory). @@ -6442,7 +6445,7 @@ public class Intent implements Parcelable, Cloneable { public static final int FLAG_ACTIVITY_RETAIN_IN_RECENTS = 0x00002000; /** - * This flag is only used in split-screen multi-window mode. The new activity will be displayed + * This flag is only used for split-screen multi-window mode. The new activity will be displayed * adjacent to the one launching it. This can only be used in conjunction with * {@link #FLAG_ACTIVITY_NEW_TASK}. Also, setting {@link #FLAG_ACTIVITY_MULTIPLE_TASK} is * required if you want a new instance of an existing activity to be created. @@ -11272,6 +11275,19 @@ public class Intent implements Parcelable, Cloneable { * @hide */ public boolean migrateExtraStreamToClipData() { + return migrateExtraStreamToClipData(AppGlobals.getInitialApplication()); + } + + /** + * Migrate any {@link #EXTRA_STREAM} in {@link #ACTION_SEND} and + * {@link #ACTION_SEND_MULTIPLE} to {@link ClipData}. Also inspects nested + * intents in {@link #ACTION_CHOOSER}. + * + * @param context app context + * @return Whether any contents were migrated. + * @hide + */ + public boolean migrateExtraStreamToClipData(Context context) { // Refuse to touch if extras already parcelled if (mExtras != null && mExtras.isParcelled()) return false; @@ -11289,7 +11305,7 @@ public class Intent implements Parcelable, Cloneable { try { final Intent intent = getParcelableExtra(EXTRA_INTENT); if (intent != null) { - migrated |= intent.migrateExtraStreamToClipData(); + migrated |= intent.migrateExtraStreamToClipData(context); } } catch (ClassCastException e) { } @@ -11299,7 +11315,7 @@ public class Intent implements Parcelable, Cloneable { for (int i = 0; i < intents.length; i++) { final Intent intent = (Intent) intents[i]; if (intent != null) { - migrated |= intent.migrateExtraStreamToClipData(); + migrated |= intent.migrateExtraStreamToClipData(context); } } } @@ -11362,13 +11378,17 @@ public class Intent implements Parcelable, Cloneable { } catch (ClassCastException e) { } } else if (isImageCaptureIntent()) { - final Uri output; + Uri output; try { output = getParcelableExtra(MediaStore.EXTRA_OUTPUT); } catch (ClassCastException e) { return false; } + if (output != null) { + output = maybeConvertFileToContentUri(context, output); + putExtra(MediaStore.EXTRA_OUTPUT, output); + setClipData(ClipData.newRawUri("", output)); addFlags(FLAG_GRANT_WRITE_URI_PERMISSION|FLAG_GRANT_READ_URI_PERMISSION); return true; @@ -11378,6 +11398,23 @@ public class Intent implements Parcelable, Cloneable { return false; } + private Uri maybeConvertFileToContentUri(Context context, Uri uri) { + if (ContentResolver.SCHEME_FILE.equals(uri.getScheme()) + && context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.R) { + File file = new File(uri.getPath()); + try { + if (!file.exists()) file.createNewFile(); + uri = MediaStore.scanFile(context.getContentResolver(), new File(uri.getPath())); + if (uri != null) { + return uri; + } + } catch (IOException e) { + Log.e(TAG, "Ignoring failure to create file " + file, e); + } + } + return uri; + } + /** * Convert the dock state to a human readable format. * @hide diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 79da1f6ab2826602aad5d7bb38a1c72daf408904..ee9bd3d259fb803978b58a566bce011ebaabee92 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -1168,7 +1168,12 @@ public class IntentFilter implements Parcelable { public int match(Uri data, boolean wildcardSupported) { String host = data.getHost(); if (host == null) { - return NO_MATCH_DATA; + if (wildcardSupported && mWild) { + // special case, if no host is provided, but the Authority is wildcard, match + return MATCH_CATEGORY_HOST; + } else { + return NO_MATCH_DATA; + } } if (false) Log.v("IntentFilter", "Match host " + host + ": " + mHost); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index bd1ee27ece9e8f0309216cacf672986742cb68bb..1a694b34474a6f918ee694db9832b5c9be105aa9 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -1243,14 +1243,7 @@ public class LauncherApps { private ParcelFileDescriptor getUriShortcutIconFd(@NonNull String packageName, @NonNull String shortcutId, int userId) { - String uri = null; - try { - uri = mService.getShortcutIconUri(mContext.getPackageName(), packageName, shortcutId, - userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - + String uri = getShortcutIconUri(packageName, shortcutId, userId); if (uri == null) { return null; } @@ -1262,6 +1255,18 @@ public class LauncherApps { } } + private String getShortcutIconUri(@NonNull String packageName, + @NonNull String shortcutId, int userId) { + String uri = null; + try { + uri = mService.getShortcutIconUri(mContext.getPackageName(), packageName, shortcutId, + userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return uri; + } + /** * Returns the icon for this shortcut, without any badging for the profile. * @@ -1357,6 +1362,17 @@ public class LauncherApps { } catch (IOException ignore) { } } + } else if (shortcut.hasIconUri()) { + String uri = getShortcutIconUri(shortcut.getPackage(), shortcut.getId(), + shortcut.getUserId()); + if (uri == null) { + return null; + } + if (shortcut.hasAdaptiveBitmap()) { + return Icon.createWithAdaptiveBitmapContentUri(uri); + } else { + return Icon.createWithContentUri(uri); + } } else if (shortcut.hasIconResource()) { return Icon.createWithResource(shortcut.getPackage(), shortcut.getIconResourceId()); } else { diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index ed75504529b922587c1c0cb16cba182f9f96c351..fc4ccd072e758af9c13a3cc574452e1bd467b9bf 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1449,6 +1449,13 @@ public class PackageInstaller { /** {@hide} */ public static final int UID_UNKNOWN = -1; + /** + * This value is derived from the maximum file name length. No package above this limit + * can ever be successfully installed on the device. + * @hide + */ + public static final int MAX_PACKAGE_NAME_LENGTH = 255; + /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int mode = MODE_INVALID; @@ -1642,6 +1649,8 @@ public class PackageInstaller { /** * Optionally set a label representing the app being installed. + * + * This value will be trimmed to the first 1000 characters. */ public void setAppLabel(@Nullable CharSequence appLabel) { this.appLabel = (appLabel != null) ? appLabel.toString() : null; @@ -1711,7 +1720,8 @@ public class PackageInstaller { * *

Initially, all restricted permissions are whitelisted but you can change * which ones are whitelisted by calling this method or the corresponding ones - * on the {@link PackageManager}. + * on the {@link PackageManager}. Only soft or hard restricted permissions on the current + * Android version are supported and any invalid entries will be removed. * * @see PackageManager#addWhitelistedRestrictedPermission(String, String, int) * @see PackageManager#removeWhitelistedRestrictedPermission(String, String, int) diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index f354bdb5a08b4433b2f9283fbab37fb5a4d80cfd..65ce1e7ef07962ef8208bcb0d740425d073e8d64 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -49,8 +49,16 @@ import java.util.Objects; * in the implementation of Parcelable in subclasses. */ public class PackageItemInfo { - /** The maximum length of a safe label, in characters */ - private static final int MAX_SAFE_LABEL_LENGTH = 50000; + + /** + * The maximum length of a safe label, in characters + * + * TODO(b/157997155): It may make sense to expose this publicly so that apps can check for the + * value and truncate the strings/use a different label, without having to hardcode and make + * assumptions about the value. + * @hide + */ + public static final int MAX_SAFE_LABEL_LENGTH = 1000; /** @hide */ public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c577d0e896b01fc2d9fdd02efd7e8c82a2432838..ea4a2a0b8c35e3209b66e1b0706a7c66b820fc2b 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -4666,8 +4666,7 @@ public abstract class PackageManager { * Marks an application exempt from having its permissions be automatically revoked when * the app is unused for an extended period of time. * - * Only the installer on record that installed the given package, or a holder of - * {@code WHITELIST_AUTO_REVOKE_PERMISSIONS} is allowed to call this. + * Only the installer on record that installed the given package is allowed to call this. * * Packages start in whitelisted state, and it is the installer's responsibility to * un-whitelist the packages it installs, unless auto-revoking permissions from that package @@ -8172,7 +8171,7 @@ public abstract class PackageManager { private static final PropertyInvalidatedCache sPackageInfoCache = new PropertyInvalidatedCache( - 16, PermissionManager.CACHE_KEY_PACKAGE_INFO) { + 32, PermissionManager.CACHE_KEY_PACKAGE_INFO) { @Override protected PackageInfo recompute(PackageInfoQuery query) { return getPackageInfoAsUserUncached( diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index c8dd4d9d9d51d0a8e1b5ddc31b5455b24f055e31..70e4e6cbf622eb6bb145c2183ed5ced8b10da55d 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -205,6 +205,7 @@ public class PackageParser { public static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m"; public static final String TAG_USES_SDK = "uses-sdk"; public static final String TAG_USES_SPLIT = "uses-split"; + public static final String TAG_PROFILEABLE = "profileable"; public static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect"; public static final String METADATA_SUPPORTS_SIZE_CHANGES = "android.supports_size_changes"; @@ -459,6 +460,9 @@ public class PackageParser { public final SigningDetails signingDetails; public final boolean coreApp; public final boolean debuggable; + // This does not represent the actual manifest structure since the 'profilable' tag + // could be used with attributes other than 'shell'. Extend if necessary. + public final boolean profilableByShell; public final boolean multiArch; public final boolean use32bitAbi; public final boolean extractNativeLibs; @@ -470,15 +474,13 @@ public class PackageParser { public final int overlayPriority; public ApkLite(String codePath, String packageName, String splitName, - boolean isFeatureSplit, - String configForSplit, String usesSplitName, boolean isSplitRequired, - int versionCode, int versionCodeMajor, - int revisionCode, int installLocation, List verifiers, - SigningDetails signingDetails, boolean coreApp, - boolean debuggable, boolean multiArch, boolean use32bitAbi, - boolean useEmbeddedDex, boolean extractNativeLibs, boolean isolatedSplits, - String targetPackageName, boolean overlayIsStatic, int overlayPriority, - int minSdkVersion, int targetSdkVersion) { + boolean isFeatureSplit, String configForSplit, String usesSplitName, + boolean isSplitRequired, int versionCode, int versionCodeMajor, int revisionCode, + int installLocation, List verifiers, SigningDetails signingDetails, + boolean coreApp, boolean debuggable, boolean profilableByShell, boolean multiArch, + boolean use32bitAbi, boolean useEmbeddedDex, boolean extractNativeLibs, + boolean isolatedSplits, String targetPackageName, boolean overlayIsStatic, + int overlayPriority, int minSdkVersion, int targetSdkVersion) { this.codePath = codePath; this.packageName = packageName; this.splitName = splitName; @@ -493,6 +495,7 @@ public class PackageParser { this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]); this.coreApp = coreApp; this.debuggable = debuggable; + this.profilableByShell = profilableByShell; this.multiArch = multiArch; this.use32bitAbi = use32bitAbi; this.useEmbeddedDex = useEmbeddedDex; @@ -1374,9 +1377,11 @@ public class PackageParser { } SigningDetails verified; if (skipVerify) { - // systemDir APKs are already trusted, save time by not verifying + // systemDir APKs are already trusted, save time by not verifying; since the signature + // is not verified and some system apps can have their V2+ signatures stripped allow + // pulling the certs from the jar signature. verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification( - apkPath, minSignatureScheme); + apkPath, SigningDetails.SignatureSchemeVersion.JAR); } else { verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme); } @@ -1573,6 +1578,7 @@ public class PackageParser { int revisionCode = 0; boolean coreApp = false; boolean debuggable = false; + boolean profilableByShell = false; boolean multiArch = false; boolean use32bitAbi = false; boolean extractNativeLibs = true; @@ -1638,6 +1644,10 @@ public class PackageParser { final String attr = attrs.getAttributeName(i); if ("debuggable".equals(attr)) { debuggable = attrs.getAttributeBooleanValue(i, false); + if (debuggable) { + // Debuggable implies profileable + profilableByShell = true; + } } if ("multiArch".equals(attr)) { multiArch = attrs.getAttributeBooleanValue(i, false); @@ -1690,6 +1700,13 @@ public class PackageParser { minSdkVersion = attrs.getAttributeIntValue(i, DEFAULT_MIN_SDK_VERSION); } } + } else if (TAG_PROFILEABLE.equals(parser.getName())) { + for (int i = 0; i < attrs.getAttributeCount(); ++i) { + final String attr = attrs.getAttributeName(i); + if ("shell".equals(attr)) { + profilableByShell = attrs.getAttributeBooleanValue(i, profilableByShell); + } + } } } @@ -1707,8 +1724,9 @@ public class PackageParser { return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode, versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails, coreApp, debuggable, - multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, isolatedSplits, - targetPackage, overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion); + profilableByShell, multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, + isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, minSdkVersion, + targetSdkVersion); } /** diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java index 653b9ec9e8f23c48e6ac0e5975d634c1a8e45330..98a20f73a1200a2345abcccdf22472bb95830937 100644 --- a/core/java/android/content/pm/PackagePartitions.java +++ b/core/java/android/content/pm/PackagePartitions.java @@ -183,17 +183,20 @@ public class PackagePartitions { /** Returns whether the partition contains the specified file in its priv-app folder. */ public boolean containsPrivApp(@NonNull File scanFile) { - return FileUtils.contains(mPrivAppFolder.getFile(), canonicalize(scanFile)); + return mPrivAppFolder != null + && FileUtils.contains(mPrivAppFolder.getFile(), canonicalize(scanFile)); } /** Returns whether the partition contains the specified file in its app folder. */ public boolean containsApp(@NonNull File scanFile) { - return FileUtils.contains(mAppFolder.getFile(), canonicalize(scanFile)); + return mAppFolder != null + && FileUtils.contains(mAppFolder.getFile(), canonicalize(scanFile)); } /** Returns whether the partition contains the specified file in its overlay folder. */ public boolean containsOverlay(@NonNull File scanFile) { - return FileUtils.contains(mOverlayFolder.getFile(), canonicalize(scanFile)); + return mOverlayFolder != null + && FileUtils.contains(mOverlayFolder.getFile(), canonicalize(scanFile)); } } diff --git a/core/java/android/content/pm/dex/ArtManagerInternal.java b/core/java/android/content/pm/dex/ArtManagerInternal.java index 62ab9e02f85804f8fbcba447a245cd875559cd4c..23fef29803e756e8453ec867b8ed01bebea0c409 100644 --- a/core/java/android/content/pm/dex/ArtManagerInternal.java +++ b/core/java/android/content/pm/dex/ArtManagerInternal.java @@ -30,5 +30,5 @@ public abstract class ArtManagerInternal { * in executes using the specified {@code abi}. */ public abstract PackageOptimizationInfo getPackageOptimizationInfo( - ApplicationInfo info, String abi); + ApplicationInfo info, String abi, String activityName); } diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index d2172d3741d18e3c72c1b03c0a269f81e9678cbc..c3e9402a389e238cc93f4580ee58841769e02da2 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -20,7 +20,6 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; -import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; @@ -303,6 +302,7 @@ public class ApkLiteParseUtils { int revisionCode = 0; boolean coreApp = false; boolean debuggable = false; + boolean profilableByShell = false; boolean multiArch = false; boolean use32bitAbi = false; boolean extractNativeLibs = true; @@ -379,6 +379,10 @@ public class ApkLiteParseUtils { switch (attr) { case "debuggable": debuggable = attrs.getAttributeBooleanValue(i, false); + if (debuggable) { + // Debuggable implies profileable + profilableByShell = true; + } break; case "multiArch": multiArch = attrs.getAttributeBooleanValue(i, false); @@ -431,6 +435,13 @@ public class ApkLiteParseUtils { minSdkVersion = attrs.getAttributeIntValue(i, DEFAULT_MIN_SDK_VERSION); } } + } else if (PackageParser.TAG_PROFILEABLE.equals(parser.getName())) { + for (int i = 0; i < attrs.getAttributeCount(); ++i) { + final String attr = attrs.getAttributeName(i); + if ("shell".equals(attr)) { + profilableByShell = attrs.getAttributeBooleanValue(i, profilableByShell); + } + } } } @@ -445,12 +456,13 @@ public class ApkLiteParseUtils { overlayPriority = 0; } - return input.success(new PackageParser.ApkLite(codePath, packageSplit.first, - packageSplit.second, isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, - versionCode, versionCodeMajor, revisionCode, installLocation, verifiers, - signingDetails, coreApp, debuggable, multiArch, use32bitAbi, useEmbeddedDex, - extractNativeLibs, isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, - minSdkVersion, targetSdkVersion)); + return input.success( + new PackageParser.ApkLite(codePath, packageSplit.first, packageSplit.second, + isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode, + versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails, + coreApp, debuggable, profilableByShell, multiArch, use32bitAbi, + useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage, + overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion)); } public static ParseResult> parsePackageSplitNames(ParseInput input, diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 317107829623e26c4d572da17e06057901512676..ab0ed51fb909f554a38974f89749ca2c88c6e52a 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -1510,7 +1510,7 @@ public class ParsingPackageUtils { Uri data = null; String dataType = null; - String host = IntentFilter.WILDCARD; + String host = null; final int numActions = intentInfo.countActions(); final int numSchemes = intentInfo.countDataSchemes(); final int numTypes = intentInfo.countDataTypes(); @@ -2748,9 +2748,11 @@ public class ParsingPackageUtils { SigningDetails verified; try { if (skipVerify) { - // systemDir APKs are already trusted, save time by not verifying + // systemDir APKs are already trusted, save time by not verifying; since the + // signature is not verified and some system apps can have their V2+ signatures + // stripped allow pulling the certs from the jar signature. verified = ApkSignatureVerifier.unsafeGetCertsWithoutVerification( - baseCodePath, minSignatureScheme); + baseCodePath, SigningDetails.SignatureSchemeVersion.JAR); } else { verified = ApkSignatureVerifier.verify(baseCodePath, minSignatureScheme); } diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java index f64560a14832114606e2223e038cbaaeba9202f0..fb8fd74545c781e94ee72ba9f3942f2960d75635 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java @@ -302,7 +302,14 @@ public class ParsedActivityUtils { } String permission = array.getNonConfigurationString(permissionAttr, 0); - activity.setPermission(permission != null ? permission : pkg.getPermission()); + if (isAlias) { + // An alias will override permissions to allow referencing an Activity through its alias + // without needing the original permission. If an alias needs the same permission, + // it must be re-declared. + activity.setPermission(permission); + } else { + activity.setPermission(permission != null ? permission : pkg.getPermission()); + } final boolean setExported = array.hasValue(exportedAttr); if (setExported) { diff --git a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java index b37b617570533c04203f0fae9b6fbf6ffd08161a..6811e06fbe7e547e185babd91179d5953cad45cf 100644 --- a/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedComponentUtils.java @@ -20,7 +20,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.ParsingUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -29,9 +32,6 @@ import android.text.TextUtils; import android.util.TypedValue; import com.android.internal.annotations.VisibleForTesting; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.result.ParseInput; -import android.content.pm.parsing.result.ParseResult; /** @hide */ class ParsedComponentUtils { @@ -60,16 +60,27 @@ class ParsedComponentUtils { component.setName(className); component.setPackageName(packageName); - if (useRoundIcon) { - component.icon = array.getResourceId(roundIconAttr, 0); + int roundIconVal = useRoundIcon ? array.getResourceId(roundIconAttr, 0) : 0; + if (roundIconVal != 0) { + component.icon = roundIconVal; + component.nonLocalizedLabel = null; + } else { + int iconVal = array.getResourceId(iconAttr, 0); + if (iconVal != 0) { + component.icon = iconVal; + component.nonLocalizedLabel = null; + } } - if (component.icon == 0) { - component.icon = array.getResourceId(iconAttr, 0); + int logoVal = array.getResourceId(logoAttr, 0); + if (logoVal != 0) { + component.logo = logoVal; } - component.logo = array.getResourceId(logoAttr, 0); - component.banner = array.getResourceId(bannerAttr, 0); + int bannerVal = array.getResourceId(bannerAttr, 0); + if (bannerVal != 0) { + component.banner = bannerVal; + } if (descriptionAttr != null) { component.descriptionRes = array.getResourceId(descriptionAttr, 0); diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 8d472da1fb7cd0e4fad3f5d675da0a1a43e8affc..e385cd2b7ecdb88d2b92fe19caf7483afceae3dc 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -26,6 +26,8 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.os.RemoteException; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; import android.util.Slog; /** @@ -82,6 +84,9 @@ public class BiometricManager { * *

Types may combined via bitwise OR into a single integer representing multiple * authenticators (e.g. DEVICE_CREDENTIAL | BIOMETRIC_WEAK). + * + * @see #canAuthenticate(int) + * @see BiometricPrompt.Builder#setAllowedAuthenticators(int) */ public interface Authenticators { /** @@ -116,22 +121,29 @@ public class BiometricManager { /** * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the - * requirements for Strong, as defined by the Android CDD. + * requirements for Class 3 (formerly Strong), as defined + * by the Android CDD. + * + *

This corresponds to {@link KeyProperties#AUTH_BIOMETRIC_STRONG} during key generation. + * + * @see KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int) */ int BIOMETRIC_STRONG = 0x000F; /** * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the - * requirements for Weak, as defined by the Android CDD. + * requirements for Class 2 (formerly Weak), as defined by + * the Android CDD. * *

Note that this is a superset of {@link #BIOMETRIC_STRONG} and is defined such that - * BIOMETRIC_STRONG | BIOMETRIC_WEAK == BIOMETRIC_WEAK. + * {@code BIOMETRIC_STRONG | BIOMETRIC_WEAK == BIOMETRIC_WEAK}. */ int BIOMETRIC_WEAK = 0x00FF; /** * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the - * requirements for Convenience, as defined by the Android CDD. + * requirements for Class 1 (formerly Convenience), as + * defined by the Android CDD. * *

This constant is intended for use by {@link android.provider.DeviceConfig} to adjust * the reported strength of a biometric sensor. It is not a valid parameter for any of the @@ -153,6 +165,11 @@ public class BiometricManager { * The non-biometric credential used to secure the device (i.e., PIN, pattern, or password). * This should typically only be used in combination with a biometric auth type, such as * {@link #BIOMETRIC_WEAK}. + * + *

This corresponds to {@link KeyProperties#AUTH_DEVICE_CREDENTIAL} during key + * generation. + * + * @see KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int) */ int DEVICE_CREDENTIAL = 1 << 15; } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 5af7cef3e2b4862555e5a9214ee2f21d6e931a69..74caceae07c969569fcad17454823de357f916fb 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -36,6 +36,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.security.identity.IdentityCredential; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; import android.text.TextUtils; import android.util.Log; @@ -371,6 +373,14 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * button on the prompt, making it an error to also call * {@link #setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. * + *

If unlocking cryptographic operation(s), it is the application's responsibility to + * request authentication with the proper set of authenticators (e.g. match the + * authenticators specified during key generation). + * + * @see KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int) + * @see KeyProperties#AUTH_BIOMETRIC_STRONG + * @see KeyProperties#AUTH_DEVICE_CREDENTIAL + * * @param authenticators A bit field representing all valid authenticator types that may be * invoked by the prompt. * @return This builder. @@ -606,8 +616,24 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** - * A wrapper class for the crypto objects supported by BiometricPrompt. Currently the framework - * supports {@link Signature}, {@link Cipher} and {@link Mac} objects. + * A wrapper class for the cryptographic operations supported by BiometricPrompt. + * + *

Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac}, and + * {@link IdentityCredential}. + * + *

Cryptographic operations in Android can be split into two categories: auth-per-use and + * time-based. This is specified during key creation via the timeout parameter of the + * {@link KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int)} API. + * + *

CryptoObjects are used to unlock auth-per-use keys via + * {@link BiometricPrompt#authenticate(CryptoObject, CancellationSignal, Executor, + * AuthenticationCallback)}, whereas time-based keys are unlocked for their specified duration + * any time the user authenticates with the specified authenticators (e.g. unlocking keyguard). + * If a time-based key is not available for use (i.e. none of the allowed authenticators have + * been unlocked recently), applications can prompt the user to authenticate via + * {@link BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} + * + * @see Builder#setAllowedAuthenticators(int) */ public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { public CryptoObject(@NonNull Signature signature) { diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index b149d7798aab2cf7130ff872ebf3896428759c5e..dc56963ffd8c894a06c74d6fb0c1fd4d7911fea4 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -4086,10 +4086,11 @@ public final class CameraCharacteristics extends CameraMetadataThe accuracy of frame timestamp synchronization between physical cameras

*

The accuracy of the frame timestamp synchronization determines the physical cameras' - * ability to start exposure at the same time. If the sensorSyncType is CALIBRATED, - * the physical camera sensors usually run in master-slave mode so that their shutter - * time is synchronized. For APPROXIMATE sensorSyncType, the camera sensors usually run in - * master-master mode, and there could be offset between their start of exposure.

+ * ability to start exposure at the same time. If the sensorSyncType is CALIBRATED, the + * physical camera sensors usually run in leader/follower mode where one sensor generates a + * timing signal for the other, so that their shutter time is synchronized. For APPROXIMATE + * sensorSyncType, the camera sensors usually run in leader/leader mode, where both sensors + * use their own timing generator, and there could be offset between their start of exposure.

*

In both cases, all images generated for a particular capture request still carry the same * timestamps, so that they can be used to look up the matching frame number and * onCaptureStarted callback.

diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 30ee32604939f0c2c1f1c8b0b8318498835eb6a8..15625cdeb8f4a7bd0ee4e4405c94946f89fca453 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -680,7 +680,7 @@ public abstract class CameraDevice implements AutoCloseable { *
*

* - *

Devices capable of streaming concurrently with other devices as described by + *

BACKWARD_COMPATIBLE devices capable of streaming concurrently with other devices as described by * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds} have the * following guaranteed streams (when streaming concurrently with other devices)

* @@ -696,10 +696,14 @@ public abstract class CameraDevice implements AutoCloseable { *
*

* + *

Devices which are not backwards-compatible, support a mandatory single stream of size sVGA with image format {@code DEPTH16} during concurrent operation. + * *

For guaranteed concurrent stream configurations:

- *

s720p refers to the camera device's resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or + *

sVGA refers to the camera device's maximum resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or + * VGA resolution (640X480) whichever is lower.

+ *

s720p refers to the camera device's maximum resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or * 720p(1280X720) whichever is lower.

- *

s1440p refers to the camera device's resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or + *

s1440p refers to the camera device's maximum resolution for that format from {@link StreamConfigurationMap#getOutputSizes} or * 1440p(1920X1440) whichever is lower.

*

MONOCHROME-capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} * includes {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}) devices @@ -707,6 +711,7 @@ public abstract class CameraDevice implements AutoCloseable { * streams with {@code Y8} in all guaranteed stream combinations for the device's hardware level * and capabilities.

* + * *

Devices capable of outputting HEIC formats ({@link StreamConfigurationMap#getOutputFormats} * contains {@link android.graphics.ImageFormat#HEIC}) will support substituting {@code JPEG} * streams with {@code HEIC} in all guaranteed stream combinations for the device's hardware diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 6bbc37a90faee28615c3df2acb4fa2636bdaa5d7..7f834afd7b30075f08f8baab65f685c4685e4b6f 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -586,13 +586,27 @@ public final class CameraManager { * priority when accessing the camera, and this method will succeed even if the camera device is * in use by another camera API client. Any lower-priority application that loses control of the * camera in this way will receive an - * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback.

+ * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback. + * Opening the same camera ID twice in the same application will similarly cause the + * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback + * being fired for the {@link CameraDevice} from the first open call and all ongoing tasks + * being droppped.

* *

Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up * for operation by calling {@link CameraDevice#createCaptureSession} and * {@link CameraDevice#createCaptureRequest}

* + *

Before API level 30, when the application tries to open multiple {@link CameraDevice} of + * different IDs and the device does not support opening such combination, either the + * {@link #openCamera} will fail and throw a {@link CameraAccessException} or one or more of + * already opened {@link CameraDevice} will be disconnected and receive + * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback. Which + * behavior will happen depends on the device implementation and can vary on different devices. + * Starting in API level 30, if the device does not support the combination of cameras being + * opened, it is guaranteed the {@link #openCamera} call will fail and none of existing + * {@link CameraDevice} will be disconnected.

+ * * - - - 544dp + */ - +package android.os; + +parcelable CarrierAssociatedAppEntry; diff --git a/core/java/android/os/CarrierAssociatedAppEntry.java b/core/java/android/os/CarrierAssociatedAppEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..13f6eb63e29c86fc368292bd81474cb1005c6361 --- /dev/null +++ b/core/java/android/os/CarrierAssociatedAppEntry.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.os; + +/** + * Represents a carrier app entry for use with {@link SystemConfigService}. + * + * @hide + */ +public final class CarrierAssociatedAppEntry implements Parcelable { + + /** + * For carrier-associated app entries that don't specify the addedInSdk XML + * attribute. + */ + public static final int SDK_UNSPECIFIED = -1; + + public final String packageName; + /** May be {@link #SDK_UNSPECIFIED}. */ + public final int addedInSdk; + + public CarrierAssociatedAppEntry(String packageName, int addedInSdk) { + this.packageName = packageName; + this.addedInSdk = addedInSdk; + } + + public CarrierAssociatedAppEntry(Parcel in) { + packageName = in.readString(); + addedInSdk = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(packageName); + dest.writeInt(addedInSdk); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public CarrierAssociatedAppEntry createFromParcel(Parcel source) { + return new CarrierAssociatedAppEntry(source); + } + + @Override + public CarrierAssociatedAppEntry[] newArray(int size) { + return new CarrierAssociatedAppEntry[size]; + } + }; +} diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 034e6a7a06c4de822ff932a3c638ffa6c2de68a9..df58a6c636f5a92d80f53d8a6ac16a8e53212d5c 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -22,6 +22,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -93,8 +94,8 @@ public class GraphicsEnvironment { private static final int GAME_DRIVER_GLOBAL_OPT_IN_OFF = 3; private ClassLoader mClassLoader; - private String mLayerPath; - private String mDebugLayerPath; + private String mLibrarySearchPaths; + private String mLibraryPermittedPaths; /** * Set up GraphicsEnvironment @@ -185,118 +186,131 @@ public class GraphicsEnvironment { } /** - * Store the layer paths available to the loader. + * Store the class loader for namespace lookup later. */ public void setLayerPaths(ClassLoader classLoader, - String layerPath, - String debugLayerPath) { + String searchPaths, + String permittedPaths) { // We have to store these in the class because they are set up before we // have access to the Context to properly set up GraphicsEnvironment mClassLoader = classLoader; - mLayerPath = layerPath; - mDebugLayerPath = debugLayerPath; + mLibrarySearchPaths = searchPaths; + mLibraryPermittedPaths = permittedPaths; + } + + /** + * Returns the debug layer paths from settings. + * Returns null if: + * 1) The application process is not debuggable or layer injection metadata flag is not + * true; Or + * 2) ENABLE_GPU_DEBUG_LAYERS is not true; Or + * 3) Package name is not equal to GPU_DEBUG_APP. + */ + public String getDebugLayerPathsFromSettings( + Bundle coreSettings, IPackageManager pm, String packageName, + ApplicationInfo ai) { + if (!debugLayerEnabled(coreSettings, packageName, ai)) { + return null; + } + Log.i(TAG, "GPU debug layers enabled for " + packageName); + String debugLayerPaths = ""; + + // Grab all debug layer apps and add to paths. + final String gpuDebugLayerApps = + coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP, ""); + if (!gpuDebugLayerApps.isEmpty()) { + Log.i(TAG, "GPU debug layer apps: " + gpuDebugLayerApps); + // If a colon is present, treat this as multiple apps, so Vulkan and GLES + // layer apps can be provided at the same time. + final String[] layerApps = gpuDebugLayerApps.split(":"); + for (int i = 0; i < layerApps.length; i++) { + String paths = getDebugLayerAppPaths(pm, layerApps[i]); + if (!paths.isEmpty()) { + // Append the path so files placed in the app's base directory will + // override the external path + debugLayerPaths += paths + File.pathSeparator; + } + } + } + return debugLayerPaths; } /** * Return the debug layer app's on-disk and in-APK lib directories */ - private static String getDebugLayerAppPaths(PackageManager pm, String app) { + private static String getDebugLayerAppPaths(IPackageManager pm, String packageName) { final ApplicationInfo appInfo; try { - appInfo = pm.getApplicationInfo(app, PackageManager.MATCH_ALL); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Debug layer app '" + app + "' not installed"); - - return null; + appInfo = pm.getApplicationInfo(packageName, PackageManager.MATCH_ALL, + UserHandle.myUserId()); + } catch (RemoteException e) { + return ""; + } + if (appInfo == null) { + Log.w(TAG, "Debug layer app '" + packageName + "' not installed"); } final String abi = chooseAbi(appInfo); - final StringBuilder sb = new StringBuilder(); sb.append(appInfo.nativeLibraryDir) - .append(File.pathSeparator); - sb.append(appInfo.sourceDir) + .append(File.pathSeparator) + .append(appInfo.sourceDir) .append("!/lib/") .append(abi); final String paths = sb.toString(); - if (DEBUG) Log.v(TAG, "Debug layer app libs: " + paths); return paths; } + private boolean debugLayerEnabled(Bundle coreSettings, String packageName, ApplicationInfo ai) { + // Only enable additional debug functionality if the following conditions are met: + // 1. App is debuggable or device is rooted or layer injection metadata flag is true + // 2. ENABLE_GPU_DEBUG_LAYERS is true + // 3. Package name is equal to GPU_DEBUG_APP + if (!isDebuggable() && !canInjectLayers(ai)) { + return false; + } + final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0); + if (enable == 0) { + return false; + } + final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP, ""); + if (packageName == null + || (gpuDebugApp.isEmpty() || packageName.isEmpty()) + || !gpuDebugApp.equals(packageName)) { + return false; + } + return true; + } + /** * Set up layer search paths for all apps - * If debuggable, check for additional debug settings */ private void setupGpuLayers( Context context, Bundle coreSettings, PackageManager pm, String packageName, ApplicationInfo ai) { + final boolean enabled = debugLayerEnabled(coreSettings, packageName, ai); String layerPaths = ""; + if (enabled) { + layerPaths = mLibraryPermittedPaths; - // Only enable additional debug functionality if the following conditions are met: - // 1. App is debuggable or device is rooted or layer injection metadata flag is true - // 2. ENABLE_GPU_DEBUG_LAYERS is true - // 3. Package name is equal to GPU_DEBUG_APP + final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS); + Log.i(TAG, "Vulkan debug layer list: " + layers); + if (layers != null && !layers.isEmpty()) { + setDebugLayers(layers); + } - if (isDebuggable() || canInjectLayers(ai)) { - - final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0); - - if (enable != 0) { - - final String gpuDebugApp = coreSettings.getString(Settings.Global.GPU_DEBUG_APP); - - if ((gpuDebugApp != null && packageName != null) - && (!gpuDebugApp.isEmpty() && !packageName.isEmpty()) - && gpuDebugApp.equals(packageName)) { - Log.i(TAG, "GPU debug layers enabled for " + packageName); - - // Prepend the debug layer path as a searchable path. - // This will ensure debug layers added will take precedence over - // the layers specified by the app. - layerPaths = mDebugLayerPath + ":"; - - // If there is a debug layer app specified, add its path. - final String gpuDebugLayerApp = - coreSettings.getString(Settings.Global.GPU_DEBUG_LAYER_APP); - - if (gpuDebugLayerApp != null && !gpuDebugLayerApp.isEmpty()) { - Log.i(TAG, "GPU debug layer app: " + gpuDebugLayerApp); - // If a colon is present, treat this as multiple apps, so Vulkan and GLES - // layer apps can be provided at the same time. - String[] layerApps = gpuDebugLayerApp.split(":"); - for (int i = 0; i < layerApps.length; i++) { - String paths = getDebugLayerAppPaths(pm, layerApps[i]); - if (paths != null) { - // Append the path so files placed in the app's base directory will - // override the external path - layerPaths += paths + ":"; - } - } - } - - final String layers = coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS); - - Log.i(TAG, "Vulkan debug layer list: " + layers); - if (layers != null && !layers.isEmpty()) { - setDebugLayers(layers); - } - - final String layersGLES = - coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES); - - Log.i(TAG, "GLES debug layer list: " + layersGLES); - if (layersGLES != null && !layersGLES.isEmpty()) { - setDebugLayersGLES(layersGLES); - } - } + final String layersGLES = + coreSettings.getString(Settings.Global.GPU_DEBUG_LAYERS_GLES); + Log.i(TAG, "GLES debug layer list: " + layersGLES); + if (layersGLES != null && !layersGLES.isEmpty()) { + setDebugLayersGLES(layersGLES); } } // Include the app's lib directory in all cases - layerPaths += mLayerPath; - + layerPaths += mLibrarySearchPaths; setLayerPaths(mClassLoader, layerPaths); } diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl index d3b029854112fb25ec1da3c20cd4be36e94f06a8..52f0ce1f054f31d83480ef06cc71ab8102bb6a90 100644 --- a/core/java/android/os/ISystemConfig.aidl +++ b/core/java/android/os/ISystemConfig.aidl @@ -30,4 +30,9 @@ interface ISystemConfig { * @see SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierAssociatedApps */ Map getDisabledUntilUsedPreinstalledCarrierAssociatedApps(); + + /** + * @see SystemConfigManager#getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries + */ + Map getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries(); } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 5d2c9d18c00c20b81f889ccdc3517b32e016b6d0..a4077fbee892e9bd1f1d64c90c77252f468a0a63 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -228,6 +228,13 @@ public class Process { */ public static final int EXT_OBB_RW_GID = 1079; + /** + * GID that corresponds to the INTERNET permission. + * Must match the value of AID_INET. + * @hide + */ + public static final int INET_GID = 3003; + /** {@hide} */ public static final int NOBODY_UID = 9999; diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 02b822a99f2add052738fbf12fb629068d682d1b..257bc5b642857be7ad0456428a253a1d2a06d69d 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -84,6 +84,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; @@ -189,6 +191,9 @@ public final class StrictMode { // Only show an annoying dialog at most every 30 seconds private static final long MIN_DIALOG_INTERVAL_MS = 30000; + // Only log a dropbox entry at most every 30 seconds + private static final long MIN_DROPBOX_INTERVAL_MS = 3000; + // How many Span tags (e.g. animations) to report. private static final int MAX_SPAN_TAGS = 20; @@ -817,6 +822,9 @@ public final class StrictMode { /** @hide */ public @NonNull Builder permitActivityLeaks() { + synchronized (StrictMode.class) { + sExpectedActivityInstanceCount.clear(); + } return disable(DETECT_VM_ACTIVITY_LEAKS); } @@ -1749,16 +1757,20 @@ public final class StrictMode { // Not perfect, but fast and good enough for dup suppression. Integer crashFingerprint = info.hashCode(); long lastViolationTime = 0; - if (mLastViolationTime != null) { - Long vtime = mLastViolationTime.get(crashFingerprint); - if (vtime != null) { - lastViolationTime = vtime; + long now = SystemClock.uptimeMillis(); + if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger + if (mLastViolationTime != null) { + Long vtime = mLastViolationTime.get(crashFingerprint); + if (vtime != null) { + lastViolationTime = vtime; + } + clampViolationTimeMap(mLastViolationTime, Math.max(MIN_LOG_INTERVAL_MS, + Math.max(MIN_DIALOG_INTERVAL_MS, MIN_DROPBOX_INTERVAL_MS))); + } else { + mLastViolationTime = new ArrayMap<>(1); } - } else { - mLastViolationTime = new ArrayMap<>(1); + mLastViolationTime.put(crashFingerprint, now); } - long now = SystemClock.uptimeMillis(); - mLastViolationTime.put(crashFingerprint, now); long timeSinceLastViolationMillis = lastViolationTime == 0 ? Long.MAX_VALUE : (now - lastViolationTime); @@ -1777,7 +1789,8 @@ public final class StrictMode { penaltyMask |= PENALTY_DIALOG; } - if (info.penaltyEnabled(PENALTY_DROPBOX) && lastViolationTime == 0) { + if (info.penaltyEnabled(PENALTY_DROPBOX) + && timeSinceLastViolationMillis > MIN_DROPBOX_INTERVAL_MS) { penaltyMask |= PENALTY_DROPBOX; } @@ -1905,9 +1918,16 @@ public final class StrictMode { } private static class AndroidCloseGuardReporter implements CloseGuard.Reporter { + + @Override public void report(String message, Throwable allocationSite) { onVmPolicyViolation(new LeakedClosableViolation(message, allocationSite)); } + + @Override + public void report(String message) { + onVmPolicyViolation(new LeakedClosableViolation(message)); + } } /** Called from Parcel.writeNoException() */ @@ -2212,6 +2232,23 @@ public final class StrictMode { @UnsupportedAppUsage private static final HashMap sLastVmViolationTime = new HashMap<>(); + /** + * Clamp the given map by removing elements with timestamp older than the given retainSince. + */ + private static void clampViolationTimeMap(final @NonNull Map violationTime, + final long retainSince) { + final Iterator> iterator = violationTime.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry e = iterator.next(); + if (e.getValue() < retainSince) { + // Remove stale entries + iterator.remove(); + } + } + // Ideally we'd cap the total size of the map, though it'll involve quickselect of topK, + // seems not worth it (saving some space immediately but they will be obsoleted soon anyway) + } + /** @hide */ public static void onVmPolicyViolation(Violation originStack) { onVmPolicyViolation(originStack, false); @@ -2235,13 +2272,17 @@ public final class StrictMode { final long now = SystemClock.uptimeMillis(); long lastViolationTime; long timeSinceLastViolationMillis = Long.MAX_VALUE; - synchronized (sLastVmViolationTime) { - if (sLastVmViolationTime.containsKey(fingerprint)) { - lastViolationTime = sLastVmViolationTime.get(fingerprint); - timeSinceLastViolationMillis = now - lastViolationTime; - } - if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) { - sLastVmViolationTime.put(fingerprint, now); + if (sLogger == LOGCAT_LOGGER) { // Don't throttle it if there is a non-default logger + synchronized (sLastVmViolationTime) { + if (sLastVmViolationTime.containsKey(fingerprint)) { + lastViolationTime = sLastVmViolationTime.get(fingerprint); + timeSinceLastViolationMillis = now - lastViolationTime; + } + if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) { + sLastVmViolationTime.put(fingerprint, now); + } + clampViolationTimeMap(sLastVmViolationTime, + now - Math.max(MIN_VM_INTERVAL_MS, MIN_LOG_INTERVAL_MS)); } } if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) { @@ -2586,8 +2627,10 @@ public final class StrictMode { return; } + // Use the instance count from InstanceTracker as initial value. Integer expected = sExpectedActivityInstanceCount.get(klass); - Integer newExpected = expected == null ? 1 : expected + 1; + Integer newExpected = + expected == null ? InstanceTracker.getInstanceCount(klass) + 1 : expected + 1; sExpectedActivityInstanceCount.put(klass, newExpected); } } diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java index 3a9ce2fa85f13ba50c5c2c1a21f25263590a03c0..12a1ffaf69c15a850a184129a409c1ed327aa1fa 100644 --- a/core/java/android/os/SystemConfigManager.java +++ b/core/java/android/os/SystemConfigManager.java @@ -88,4 +88,29 @@ public class SystemConfigManager { return Collections.emptyMap(); } } + + /** + * Returns a map that describes helper apps associated with carrier apps that, like the apps + * returned by {@link #getDisabledUntilUsedPreinstalledCarrierApps()}, should be disabled until + * the correct SIM is inserted into the device. + * + *

TODO(b/159069037) expose this and get rid of the other method that omits SDK version. + * + * @return A map with keys corresponding to package names returned by + * {@link #getDisabledUntilUsedPreinstalledCarrierApps()} and values as lists of package + * names of helper apps and the SDK versions when they were first added. + * + * @hide + */ + @RequiresPermission(Manifest.permission.READ_CARRIER_APP_INFO) + public @NonNull Map> + getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries() { + try { + return (Map>) + mInterface.getDisabledUntilUsedPreinstalledCarrierAssociatedAppEntries(); + } catch (RemoteException e) { + Log.e(TAG, "Caught remote exception", e); + return Collections.emptyMap(); + } + } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 7845200f4bf7ceac5dfd096b66396a5217789182..2465b0e418766501f92d2251a7db8991f03ae0a4 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2699,15 +2699,12 @@ public class UserManager { * @param name the user's name * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}. * @param flags UserInfo flags that specify user properties. - * @return the {@link UserInfo} object for the created user, - * or throws {@link UserOperationException} if the user could not be created - * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above - * (otherwise returns {@code null}). + * @return the {@link UserInfo} object for the created user, or {@code null} if the user + * could not be created. * - * @throws UserOperationException if the user could not be created and the calling app is - * targeting {@link android.os.Build.VERSION_CODES#R} or above. - * @hide * @see UserInfo + * + * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) @@ -2716,8 +2713,7 @@ public class UserManager { try { return mService.createUserWithThrow(name, userType, flags); } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + return null; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2743,25 +2739,19 @@ public class UserManager { * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION}. * * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}. - * @return the {@link UserInfo} object for the created user, - * or throws {@link UserOperationException} if the user could not be created - * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above - * (otherwise returns {@code null}). - * - * @throws UserOperationException if the user could not be created and the calling app is - * targeting {@link android.os.Build.VERSION_CODES#R} or above. + * @return the {@link UserInfo} object for the created user. * + * @throws UserOperationException if the user could not be created. * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public @Nullable UserInfo preCreateUser(@NonNull String userType) + public @NonNull UserInfo preCreateUser(@NonNull String userType) throws UserOperationException { try { return mService.preCreateUserWithThrow(userType); } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + throw UserOperationException.from(e); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2771,18 +2761,14 @@ public class UserManager { * Creates a guest user and configures it. * @param context an application context * @param name the name to set for the user - * @return the {@link UserInfo} object for the created user, - * or throws {@link UserOperationException} if the user could not be created - * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above - * (otherwise returns {@code null}). + * @return the {@link UserInfo} object for the created user, or {@code null} if the user + * could not be created. * - * @throws UserOperationException if the user could not be created and the calling app is - * targeting {@link android.os.Build.VERSION_CODES#R} or above. * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public UserInfo createGuest(Context context, String name) throws UserOperationException { + public UserInfo createGuest(Context context, String name) { UserInfo guest = null; try { guest = mService.createUserWithThrow(name, USER_TYPE_FULL_GUEST, 0); @@ -2791,8 +2777,7 @@ public class UserManager { Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id); } } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + return null; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2902,26 +2887,20 @@ public class UserManager { * @param userId new user will be a profile of this user. * @param disallowedPackages packages that will not be installed in the profile being created. * - * @return the {@link UserInfo} object for the created user, - * or throws {@link UserOperationException} if the user could not be created - * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above - * (otherwise returns {@code null}). + * @return the {@link UserInfo} object for the created user, or {@code null} if the user could + * not be created. * - * @throws UserOperationException if the user could not be created and the calling app is - * targeting {@link android.os.Build.VERSION_CODES#R} or above. * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) public UserInfo createProfileForUser(String name, @NonNull String userType, - @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) - throws UserOperationException { + @UserInfoFlag int flags, @UserIdInt int userId, String[] disallowedPackages) { try { return mService.createProfileForUserWithThrow(name, userType, flags, userId, disallowedPackages); } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + return null; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2938,13 +2917,12 @@ public class UserManager { Manifest.permission.CREATE_USERS}) public UserInfo createProfileForUserEvenWhenDisallowed(String name, @NonNull String userType, @UserInfoFlag int flags, @UserIdInt int userId, - String[] disallowedPackages) throws UserOperationException { + String[] disallowedPackages) { try { return mService.createProfileForUserEvenWhenDisallowedWithThrow(name, userType, flags, userId, disallowedPackages); } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + return null; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -2955,18 +2933,14 @@ public class UserManager { * restrictions and adds shared accounts. * * @param name profile's name - * @return the {@link UserInfo} object for the created user, - * or throws {@link UserOperationException} if the user could not be created - * and calling app is targeting {@link android.os.Build.VERSION_CODES#R} or above - * (otherwise returns {@code null}). + * @return the {@link UserInfo} object for the created user, or {@code null} if the user + * could not be created. * - * @throws UserOperationException if the user could not be created and the calling app is - * targeting {@link android.os.Build.VERSION_CODES#R} or above. * @hide */ @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) - public UserInfo createRestrictedProfile(String name) throws UserOperationException { + public UserInfo createRestrictedProfile(String name) { try { UserHandle parentUserHandle = Process.myUserHandle(); UserInfo user = mService.createRestrictedProfileWithThrow(name, @@ -2977,8 +2951,7 @@ public class UserManager { } return user; } catch (ServiceSpecificException e) { - return returnNullOrThrowUserOperationException(e, - mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R); + return null; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -4009,15 +3982,15 @@ public class UserManager { * Sets the user's photo. * @param userId the user for whom to change the photo. * @param icon the bitmap to set as the photo. + * * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_USERS) - public void setUserIcon(@UserIdInt int userId, @NonNull Bitmap icon) - throws UserOperationException { + public void setUserIcon(@UserIdInt int userId, @NonNull Bitmap icon) { try { mService.setUserIcon(userId, icon); } catch (ServiceSpecificException e) { - throw UserOperationException.from(e); + return; } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -4027,6 +4000,10 @@ public class UserManager { * Sets the context user's photo. * * @param icon the bitmap to set as the photo. + * + * @throws UserOperationException according to the function signature, but may not actually + * throw it in practice. Catch RuntimeException instead. + * * @hide */ @SystemApi @@ -4090,26 +4067,19 @@ public class UserManager { public static int getMaxSupportedUsers() { // Don't allow multiple users on certain builds if (android.os.Build.ID.startsWith("JVP")) return 1; - if (ActivityManager.isLowRamDeviceStatic()) { - // Low-ram devices are Svelte. Most of the time they don't get multi-user. - if ((Resources.getSystem().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK) - != Configuration.UI_MODE_TYPE_TELEVISION) { - return 1; - } - } return SystemProperties.getInt("fw.max_users", Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers)); } /** - * Returns true if the user switcher should be shown. - * I.e., returns whether the user switcher is enabled and there is something actionable to show. + * Returns true if the user switcher is enabled (regardless of whether there is anything + * interesting for it to show). * - * @return true if user switcher should be shown. + * @return true if user switcher is enabled * @hide */ public boolean isUserSwitcherEnabled() { - return isUserSwitcherEnabled(false); + return isUserSwitcherEnabled(true); } /** diff --git a/core/java/android/os/Users.md b/core/java/android/os/Users.md index 3bbbe5452fd3612c515e06b175820a2c61ab1572..b019b0dc178be989a04c6c77bca10478443639fa 100644 --- a/core/java/android/os/Users.md +++ b/core/java/android/os/Users.md @@ -18,54 +18,80 @@ ## Concepts -### User +### Users and profiles -A user of a device e.g. usually a human being. Each user has its own home screen. +#### User -#### User Profile +A user is a representation of a person using a device, with their own distinct application data +and some unique settings. Throughout this document, the word 'user' will be used in this technical +sense, i.e. for this virtual environment, whereas the word 'person' will be used to denote an actual +human interacting with the device. -A user can have multiple profiles. E.g. one for the private life and one for work. Each profile -has a different set of apps and accounts but they share one home screen. All profiles of a -profile group can be active at the same time. - -Each profile has a separate [`userId`](#int-userid). Unless needed user profiles are treated as -completely separate users. +Each user has a separate [`userId`](#int-userid). #### Profile Group -All user profiles that share a home screen. You can list the profiles of a user via -`UserManager#getEnabledProfiles` (you usually don't deal with disabled profiles) +Often, there is a 1-to-1 mapping of people who use a device to 'users'; e.g. there may be two users +on a device - the owner and a guest, each with their own separate home screen. -#### Foreground user vs background user +However, Android also supports multiple profiles for a single person, e.g. one for their private +life and one for work, both sharing a single home screen. +Each profile in a profile group is a distinct user, with a unique [`userId`](#int-userid), and have +a different set of apps and accounts, +but they share a single UI, single launcher, and single wallpaper. +All profiles of a profile group can be active at the same time. -Only a single user profile group can be in the foreground. This is the user profile the user -currently interacts with. +You can list the profiles of a user via `UserManager#getEnabledProfiles` (you usually don't deal +with disabled profiles) -#### Parent user (profile) +#### Parent user -The main profile of a profile group, usually the personal (as opposed to work) profile. Get this via -`UserManager#getProfileParent` (returns `null` if the user does not have profiles) +The main user of a profile group, to which the other profiles of the group 'belong'. +This is usually the personal (as opposed to work) profile. Get this via +`UserManager#getProfileParent` (returns `null` if the user does not have profiles). -#### Managed user (profile) +#### Profile (Managed profile) -The other profiles of a profile group. The name comes from the fact that these profiles are usually +A profile of the parent user, i.e. a profile belonging to the same profile group as a parent user, +with whom they share a single home screen. +Currently, the only type of profile supported in AOSP is a 'Managed Profile'. +The name comes from the fact that these profiles are usually managed by a device policy controller app. You can create a managed profile from within the device policy controller app on your phone. +Note that, as a member of the profile group, the parent user may sometimes also be considered a +'profile', but generally speaking, the word 'profile' denotes a user that is subordinate to a +parent. + +#### Foreground user vs background user + +Only a single user can be in the foreground. +This is the user with whom the person using the device is currently interacting, or, in the case +of profiles, the parent profile of this user. +All other running users are background users. +Some users may not be running at all, neither in the foreground nor the background. + #### Account -An account of a user profile with a (usually internet based) service. E.g. aname@gmail.com or -aname@yahoo.com. Each profile can have multiple accounts. A profile does not have to have a +An account of a user with a (usually internet based) service. E.g. aname@gmail.com or +aname@yahoo.com. Each user can have multiple accounts. A user does not have to have a account. +#### System User + +The user with [`userId`](#int-userid) 0 denotes the system user, which is always required to be +running. + +On most devices, the system user is also used by the primary person using the device; however, +on certain types of devices, the system user may be a stand-alone user, not intended for direct +human interaction. + ## Data types ### int userId -... usually marked as `@UserIdInt` - -The id of a user profile. List all users via `adb shell dumpsys user`. There is no data type for a -user, all you can do is using the user id of the parent profile as a proxy for the user. +The id of a user. List all users via `adb shell dumpsys user`. +In code, these are sometimes marked as `@UserIdInt`. ### int uid @@ -97,10 +123,10 @@ mechanism should be access controlled by permissions. A system service should deal with users being started and stopped by overriding `SystemService.onSwitchUser` and `SystemService.onStopUser`. -If users profiles become inactive the system should stop all apps of this profile from interacting +If a user become inactive the system should stop all apps of this user from interacting with other apps or the system. -Another important lifecycle event is `onUnlockUser`. Only for unlocked user profiles you can access +Another important lifecycle event is `onUnlockUser`. Only for an unlocked user can you access all data, e.g. which packages are installed. You only want to deal with user profiles that diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index fd7cdda3808a436cdc3dd4fa59e54ff8719dc0a9..06254574401b70e8236e668a4f95769d25b4db59 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -221,15 +221,16 @@ public abstract class VibrationEffect implements Parcelable { * * 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. + * 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. *

* To cause the pattern to repeat, pass the index into the timings array at which to start the * repetition, or -1 to disable repeating. *

* - * @param timings The timing values of the timing / amplitude pairs. Timing values of 0 - * will cause the pair to be ignored. + * @param timings The timing values, in milliseconds, of the timing / amplitude pairs. Timing + * values of 0 will cause the pair to be ignored. * @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. diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index a4c99c006d8089dc17a5410a6b1048b6b2b21dbd..39038f555044e038a1ef7c450f31801fbed4b4b7 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -1318,15 +1318,15 @@ public class ZygoteProcess { Process.ProcessStartResult result; try { - // As app zygote is for generating isolated process, at the end it can't access - // apps data, so doesn't need to its data info. + // We will bind mount app data dirs so app zygote can't access /data/data, while + // we don't need to bind mount storage dirs as /storage won't be mounted. result = startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo, abi, instructionSet, null /* appDataDir */, null /* invokeWith */, true /* startChildZygote */, null /* packageName */, ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */, null /* disabledCompatChanges */, null /* pkgDataInfoMap */, - null /* whitelistedDataInfoMap */, false /* bindMountAppsData*/, + null /* whitelistedDataInfoMap */, true /* bindMountAppsData*/, /* bindMountAppStorageDirs */ false, extraArgs); } catch (ZygoteStartFailedEx ex) { diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index 220ce22ded5c1d13f988746179c321529107b2ae..61e6a05fce3700375790d88545516b0faa837463 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -110,6 +110,11 @@ interface IIncrementalService { */ void deleteStorage(int storageId); + /** + * Permanently disable readlogs reporting for a storage given its ID. + */ + void disableReadLogs(int storageId); + /** * Setting up native library directories and extract native libs onto a storage if needed. */ diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java index 863d86ef88c9b5cfbae820ea2e9141b56f59f0c4..31ccf95ba16f7b364ebab2c0063410e6cfa44a6b 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -152,6 +152,13 @@ public final class IncrementalFileStorages { } } + /** + * Permanently disables readlogs. + */ + public void disableReadLogs() { + mDefaultStorage.disableReadLogs(); + } + /** * Resets the states and unbinds storage instances for an installation session. * TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index 6200a38fe13c61030c28852ac338449f5df4c474..ca6114f29b9cb2204e1727ec42d1c4ee0504e45e 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -417,6 +417,17 @@ public final class IncrementalStorage { private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256 private static final int INCFS_MAX_ADD_DATA_SIZE = 128; + /** + * Permanently disable readlogs collection. + */ + public void disableReadLogs() { + try { + mService.disableReadLogs(mId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + /** * Deserialize and validate v4 signature bytes. */ diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 4ca48cb3e57cccd86e1da029d263daaed349cffb..0abf8ae352afa8b128692ddcd9b2adedfdbd3bb8 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -16,9 +16,11 @@ package android.os.storage; +import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.OP_LEGACY_STORAGE; +import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE; import static android.app.AppOpsManager.OP_READ_MEDIA_AUDIO; import static android.app.AppOpsManager.OP_READ_MEDIA_IMAGES; @@ -1365,6 +1367,7 @@ public class StorageManager { String[] packageNames = ActivityThread.getPackageManager().getPackagesForUid( android.os.Process.myUid()); if (packageNames == null || packageNames.length <= 0) { + Log.w(TAG, "Missing package names; no storage volumes available"); return new StorageVolume[0]; } packageName = packageNames[0]; @@ -1372,6 +1375,7 @@ public class StorageManager { final int uid = ActivityThread.getPackageManager().getPackageUid(packageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId); if (uid <= 0) { + Log.w(TAG, "Missing UID; no storage volumes available"); return new StorageVolume[0]; } return storageManager.getVolumeList(uid, packageName, flags); @@ -1851,7 +1855,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionReadAudio(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) { return false; } @@ -1862,7 +1866,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionWriteAudio(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) { return false; } @@ -1873,7 +1877,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionReadVideo(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) { return false; } @@ -1884,7 +1888,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionWriteVideo(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) { return false; } @@ -1895,7 +1899,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionReadImages(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) { return false; } @@ -1906,7 +1910,7 @@ public class StorageManager { /** {@hide} */ public boolean checkPermissionWriteImages(boolean enforce, int pid, int uid, String packageName, @Nullable String featureId) { - if (!checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, + if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) { return false; } @@ -1914,6 +1918,24 @@ public class StorageManager { OP_WRITE_MEDIA_IMAGES); } + private boolean checkExternalStoragePermissionAndAppOp(boolean enforce, + int pid, int uid, String packageName, @Nullable String featureId, String permission, + int op) { + // First check if app has MANAGE_EXTERNAL_STORAGE. + final int mode = mAppOps.noteOpNoThrow(OP_MANAGE_EXTERNAL_STORAGE, uid, packageName, + featureId, null); + if (mode == AppOpsManager.MODE_ALLOWED) { + return true; + } + if (mode == AppOpsManager.MODE_DEFAULT && mContext.checkPermission( + MANAGE_EXTERNAL_STORAGE, pid, uid) == PERMISSION_GRANTED) { + return true; + } + // If app doesn't have MANAGE_EXTERNAL_STORAGE, then check if it has requested granular + // permission. + return checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, permission, op); + } + /** {@hide} */ @VisibleForTesting public @NonNull ParcelFileDescriptor openProxyFileDescriptor( diff --git a/core/java/android/os/strictmode/LeakedClosableViolation.java b/core/java/android/os/strictmode/LeakedClosableViolation.java index c795a6b89ec01305532df4c91b26695f347aa5fd..a2b02833afa0bccf3aabebc426a22e91745fd732 100644 --- a/core/java/android/os/strictmode/LeakedClosableViolation.java +++ b/core/java/android/os/strictmode/LeakedClosableViolation.java @@ -21,4 +21,9 @@ public final class LeakedClosableViolation extends Violation { super(message); initCause(allocationSite); } + + /** @hide */ + public LeakedClosableViolation(String message) { + super(message); + } } diff --git a/core/java/android/os/strictmode/Violation.java b/core/java/android/os/strictmode/Violation.java index 31c7d584fd6548c3caecc606e0652c91bf3d08b0..0edb78a6424333f74dab4fc681e94f76bf3f2876 100644 --- a/core/java/android/os/strictmode/Violation.java +++ b/core/java/android/os/strictmode/Violation.java @@ -18,7 +18,58 @@ package android.os.strictmode; /** Root class for all StrictMode violations. */ public abstract class Violation extends Throwable { + private int mHashCode; + private boolean mHashCodeValid; + Violation(String message) { super(message); } + + @Override + public int hashCode() { + synchronized (this) { + if (mHashCodeValid) { + return mHashCode; + } + final String message = getMessage(); + final Throwable cause = getCause(); + int hashCode = message != null ? message.hashCode() : getClass().hashCode(); + hashCode = hashCode * 37 + calcStackTraceHashCode(getStackTrace()); + hashCode = hashCode * 37 + (cause != null ? cause.toString().hashCode() : 0); + mHashCodeValid = true; + return mHashCode = hashCode; + } + } + + @Override + public synchronized Throwable initCause(Throwable cause) { + mHashCodeValid = false; + return super.initCause(cause); + } + + @Override + public void setStackTrace(StackTraceElement[] stackTrace) { + super.setStackTrace(stackTrace); + synchronized (this) { + mHashCodeValid = false; + } + } + + @Override + public synchronized Throwable fillInStackTrace() { + mHashCodeValid = false; + return super.fillInStackTrace(); + } + + private static int calcStackTraceHashCode(final StackTraceElement[] stackTrace) { + int hashCode = 17; + if (stackTrace != null) { + for (int i = 0; i < stackTrace.length; i++) { + if (stackTrace[i] != null) { + hashCode = hashCode * 37 + stackTrace[i].hashCode(); + } + } + } + return hashCode; + } } diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index 235b0830b9aaa4cc80d74949c9493487a21ab122..e23102113e9f1f374c339ebc107e91bfa2bd05ff 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -71,7 +71,7 @@ interface IPermissionManager { void grantRuntimePermission(String packageName, String permName, int userId); - void revokeRuntimePermission(String packageName, String permName, int userId); + void revokeRuntimePermission(String packageName, String permName, int userId, String reason); void resetRuntimePermissions(); diff --git a/core/java/android/permission/Permissions.md b/core/java/android/permission/Permissions.md index 2bf08e2ff2d43aa30ee9bf6323562eae528ac0a7..1ef3ad211cee96b75303050f60ab140d9718b2e5 100644 --- a/core/java/android/permission/Permissions.md +++ b/core/java/android/permission/Permissions.md @@ -706,9 +706,9 @@ App-op permissions are user-switchable permissions that are not runtime permissi be used for permissions that are really only meant to be ever granted to a very small amount of apps. Traditionally granting these permissions is intentionally very heavy weight so that the user really needs to understand the use case. For example one use case is the -`INTERACT_ACROSS_PROFILES` permission that allows apps of different -[user profiles](../os/Users.md#user-profile) to interact. Of course this is breaking a very basic -security container and hence should only every be granted with a lot of care. +`INTERACT_ACROSS_PROFILES` permission that allows apps of different users within the same +[profile group](../os/Users.md#profile-group) to interact. Of course this is breaking a very basic +security container and hence should only ever be granted with a lot of care. **Warning:** Most app-op permissions follow this logic, but most of them also have exceptions and special behavior. Hence this section is a guideline, not a rule. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1b19e1290121b749d0573cb952f148a321374939..64d9c9dcc6e0f2584e716bde437931463b95a916 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; @@ -1777,6 +1778,15 @@ public final class Settings { public static final String ACTION_NOTIFICATION_SETTINGS = "android.settings.NOTIFICATION_SETTINGS"; + /** + * Activity Action: Show conversation settings. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CONVERSATION_SETTINGS + = "android.settings.CONVERSATION_SETTINGS"; + /** * Activity Action: Show notification history screen. * @@ -1887,6 +1897,15 @@ public final class Settings { public static final String ACTION_DEVICE_CONTROLS_SETTINGS = "android.settings.ACTION_DEVICE_CONTROLS_SETTINGS"; + /** + * Activity Action: Show media control settings + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MEDIA_CONTROLS_SETTINGS = + "android.settings.ACTION_MEDIA_CONTROLS_SETTINGS"; + /** * Activity Action: Show a dialog with disabled by policy message. *

If an user action is disabled by policy, this dialog can be triggered to let @@ -1962,6 +1981,10 @@ public final class Settings { * Input: Nothing. *

* Output: Nothing. + *

+ * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS"; @@ -6229,6 +6252,8 @@ public final class Settings { * determines if the IME should be shown when a hard keyboard is attached. * @hide */ + @TestApi + @SuppressLint("NoSettingsProvider") public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; /** @@ -8505,14 +8530,15 @@ public final class Settings { public static final int VR_DISPLAY_MODE_OFF = 1; /** - * Whether CarrierAppUtils#disableCarrierAppsUntilPrivileged has been executed at least - * once. + * The latest SDK version that CarrierAppUtils#disableCarrierAppsUntilPrivileged has been + * executed for. * *

This is used to ensure that we only take one pass which will disable apps that are not * privileged (if any). From then on, we only want to enable apps (when a matching SIM is * inserted), to avoid disabling an app that the user might actively be using. * - *

Will be set to 1 once executed. + *

Will be set to {@link android.os.Build.VERSION#SDK_INT} once executed. Note that older + * SDK versions prior to R set 1 for this value. * * @hide */ @@ -8893,6 +8919,15 @@ public final class Settings { */ public static final String PEOPLE_STRIP = "people_strip"; + /** + * Whether or not to enable media resumption + * When enabled, media controls in quick settings will populate on boot and persist if + * resumable via a MediaBrowserService. + * @see Settings.Global#SHOW_MEDIA_ON_QUICK_SETTINGS + * @hide + */ + public static final String MEDIA_CONTROLS_RESUME = "qs_media_resumption"; + /** * Controls if window magnification is enabled. * @hide @@ -11974,7 +12009,7 @@ public final class Settings { * @see #ENABLE_RESTRICTED_BUCKET * @hide */ - public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 1; + public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 0; /** * Whether or not app auto restriction is enabled. When it is enabled, settings app will @@ -14244,15 +14279,6 @@ public final class Settings { */ public static final String KERNEL_CPU_THREAD_READER = "kernel_cpu_thread_reader"; - /** - * Persistent user id that is last logged in to. - * - * They map to user ids, for example, 10, 11, 12. - * - * @hide - */ - public static final String LAST_ACTIVE_USER_ID = "last_active_persistent_user_id"; - /** * Whether we've enabled native flags health check on this device. Takes effect on * reboot. The value "1" enables native flags health check; otherwise it's disabled. diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index b34268d04238fe9fc714e52a6accbba57d031e09..a2489b9b68d9d15944f78c0f073e16ef804686bc 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4324,6 +4324,15 @@ public final class Telephony { */ public static final String ETWS_WARNING_TYPE = "etws_warning_type"; + /** + * ETWS (Earthquake and Tsunami Warning System) primary message or not (ETWS alerts only). + *

See {@link android.telephony.SmsCbEtwsInfo}

+ *

Type: BOOLEAN

+ * + * @hide // TODO: Unhide this for S. + */ + public static final String ETWS_IS_PRIMARY = "etws_is_primary"; + /** * CMAS (Commercial Mobile Alert System) message class (CMAS alerts only). *

See {@link android.telephony.SmsCbCmasInfo}

@@ -4464,37 +4473,6 @@ public final class Telephony { CMAS_URGENCY, CMAS_CERTAINTY }; - - /** - * Query columns for instantiating {@link android.telephony.SmsCbMessage} objects. - * @hide - */ - public static final String[] QUERY_COLUMNS_FWK = { - _ID, - SLOT_INDEX, - SUBSCRIPTION_ID, - GEOGRAPHICAL_SCOPE, - PLMN, - LAC, - CID, - SERIAL_NUMBER, - SERVICE_CATEGORY, - LANGUAGE_CODE, - MESSAGE_BODY, - MESSAGE_FORMAT, - MESSAGE_PRIORITY, - ETWS_WARNING_TYPE, - CMAS_MESSAGE_CLASS, - CMAS_CATEGORY, - CMAS_RESPONSE_TYPE, - CMAS_SEVERITY, - CMAS_URGENCY, - CMAS_CERTAINTY, - RECEIVED_TIME, - MESSAGE_BROADCASTED, - GEOMETRIES, - MAXIMUM_WAIT_TIME - }; } /** diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 08aa534be15276364f63e8a762dc0abb1043c48a..2d99c413cc89d8b445d522e7c606de186f4c33e7 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -312,7 +312,12 @@ public final class Dataset implements Parcelable { * setting it to the {@link * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you * provide a dataset in the result, it will replace the authenticated dataset and - * will be immediately filled in. If you provide a response, it will replace the + * will be immediately filled in. An exception to this behavior is if the original + * dataset represents a pinned inline suggestion (i.e. any of the field in the dataset + * has a pinned inline presentation, see {@link InlinePresentation#isPinned()}), then + * the original dataset will not be replaced, + * so that it can be triggered as a pending intent again. + * If you provide a response, it will replace the * current response and the UI will be refreshed. For example, if you provided * credit card information without the CVV for the data set in the {@link FillResponse * response} then the returned data set should contain the CVV entry. diff --git a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl index bf0bb9e2a41f54eefd52e7da7776177df2c0220a..7cd372fe97d8426cef6a3de439b89cccda1c7c81 100644 --- a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl +++ b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl @@ -29,6 +29,12 @@ import android.service.autofill.InlinePresentation; oneway interface IInlineSuggestionRenderService { void renderSuggestion(in IInlineSuggestionUiCallback callback, in InlinePresentation presentation, int width, int height, - in IBinder hostInputToken, int displayId); + in IBinder hostInputToken, int displayId, int userId, int sessionId); void getInlineSuggestionsRendererInfo(in RemoteCallback callback); + + /** + * Releases the inline suggestion SurfaceControlViewHosts hosted in the service, for the + * provided userId and sessionId. + */ + void destroySuggestionViews(int userId, int sessionId); } diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java index 9cf1b87f7eabd9408d1164eeebd32a797c335f7e..914169485979831e18c77339dad8f8c2e9188940 100644 --- a/core/java/android/service/autofill/InlinePresentation.java +++ b/core/java/android/service/autofill/InlinePresentation.java @@ -50,7 +50,11 @@ public final class InlinePresentation implements Parcelable { /** * Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the - * host. + * host. However, it's eventually up to the host whether the UI is pinned or not. + * + *

Also a {@link Dataset} with a pinned inline presentation will not be replaced by the + * new data set returned from authentication intent. See + * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)} for more information. */ private final boolean mPinned; @@ -90,7 +94,11 @@ public final class InlinePresentation implements Parcelable { * Specifies the UI specification for the inline suggestion. * @param pinned * Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the - * host. + * host. However, it's eventually up to the host whether the UI is pinned or not. + * + *

Also a {@link Dataset} with a pinned inline presentation will not be replaced by the + * new data set returned from authentication intent. See + * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)} for more information. */ @DataClass.Generated.Member public InlinePresentation( @@ -126,7 +134,11 @@ public final class InlinePresentation implements Parcelable { /** * Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the - * host. + * host. However, it's eventually up to the host whether the UI is pinned or not. + * + *

Also a {@link Dataset} with a pinned inline presentation will not be replaced by the + * new data set returned from authentication intent. See + * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)} for more information. */ @DataClass.Generated.Member public boolean isPinned() { @@ -232,7 +244,7 @@ public final class InlinePresentation implements Parcelable { }; @DataClass.Generated( - time = 1586992400667L, + time = 1593131904745L, codegenVersion = "1.0.15", sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java", inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size(min=0L) java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)") diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java index 3ea443bab3f803427ecc654b1b7eaa9f32dddca2..839caff5c3d4dd973d23f2b10a637f07cf519b67 100644 --- a/core/java/android/service/autofill/InlineSuggestionRenderService.java +++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java @@ -41,6 +41,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.lang.ref.WeakReference; /** @@ -64,7 +66,7 @@ public abstract class InlineSuggestionRenderService extends Service { public static final String SERVICE_INTERFACE = "android.service.autofill.InlineSuggestionRenderService"; - private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true); + private final Handler mMainHandler = new Handler(Looper.getMainLooper(), null, true); private IInlineSuggestionUiCallback mCallback; @@ -82,7 +84,7 @@ public abstract class InlineSuggestionRenderService extends Service { Boolean newValue) { if (evicted) { Log.w(TAG, - "Hit max=100 entries in the cache. Releasing oldest one to make " + "Hit max=30 entries in the cache. Releasing oldest one to make " + "space."); key.releaseSurfaceControlViewHost(); } @@ -130,7 +132,7 @@ public abstract class InlineSuggestionRenderService extends Service { private void handleRenderSuggestion(IInlineSuggestionUiCallback callback, InlinePresentation presentation, int width, int height, IBinder hostInputToken, - int displayId) { + int displayId, int userId, int sessionId) { if (hostInputToken == null) { try { callback.onError(); @@ -192,15 +194,23 @@ public abstract class InlineSuggestionRenderService extends Service { } return true; }); - - try { - InlineSuggestionUiImpl uiImpl = new InlineSuggestionUiImpl(host, mHandler); - mActiveInlineSuggestions.put(uiImpl, true); - callback.onContent(new InlineSuggestionUiWrapper(uiImpl), host.getSurfacePackage(), - measuredSize.getWidth(), measuredSize.getHeight()); - } catch (RemoteException e) { - Log.w(TAG, "RemoteException calling onContent()"); - } + final InlineSuggestionUiImpl uiImpl = new InlineSuggestionUiImpl(host, mMainHandler, + userId, sessionId); + mActiveInlineSuggestions.put(uiImpl, true); + + // We post the callback invocation to the end of the main thread handler queue, to make + // sure the callback happens after the views are drawn. This is needed because calling + // {@link SurfaceControlViewHost#setView()} will post a task to the main thread + // to draw the view asynchronously. + mMainHandler.post(() -> { + try { + callback.onContent(new InlineSuggestionUiWrapper(uiImpl), + host.getSurfacePackage(), + measuredSize.getWidth(), measuredSize.getHeight()); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException calling onContent()"); + } + }); } finally { updateDisplay(Display.DEFAULT_DISPLAY); } @@ -211,6 +221,18 @@ public abstract class InlineSuggestionRenderService extends Service { callback.sendResult(rendererInfo); } + private void handleDestroySuggestionViews(int userId, int sessionId) { + Log.v(TAG, "handleDestroySuggestionViews called for " + userId + ":" + sessionId); + for (final InlineSuggestionUiImpl inlineSuggestionUi : + mActiveInlineSuggestions.snapshot().keySet()) { + if (inlineSuggestionUi.mUserId == userId + && inlineSuggestionUi.mSessionId == sessionId) { + Log.v(TAG, "Destroy " + inlineSuggestionUi); + inlineSuggestionUi.releaseSurfaceControlViewHost(); + } + } + } + /** * A wrapper class around the {@link InlineSuggestionUiImpl} to ensure it's not strongly * reference by the remote system server process. @@ -253,10 +275,15 @@ public abstract class InlineSuggestionRenderService extends Service { private SurfaceControlViewHost mViewHost; @NonNull private final Handler mHandler; + private final int mUserId; + private final int mSessionId; - InlineSuggestionUiImpl(SurfaceControlViewHost viewHost, Handler handler) { + InlineSuggestionUiImpl(SurfaceControlViewHost viewHost, Handler handler, int userId, + int sessionId) { this.mViewHost = viewHost; this.mHandler = handler; + this.mUserId = userId; + this.mSessionId = sessionId; } /** @@ -295,6 +322,16 @@ public abstract class InlineSuggestionRenderService extends Service { } } + /** @hide */ + @Override + protected final void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, + @NonNull String[] args) { + pw.println("mActiveInlineSuggestions: " + mActiveInlineSuggestions.size()); + for (InlineSuggestionUiImpl impl : mActiveInlineSuggestions.snapshot().keySet()) { + pw.printf("ui: [%s] - [%d] [%d]\n", impl, impl.mUserId, impl.mSessionId); + } + } + @Override @Nullable public final IBinder onBind(@NonNull Intent intent) { @@ -304,19 +341,26 @@ public abstract class InlineSuggestionRenderService extends Service { @Override public void renderSuggestion(@NonNull IInlineSuggestionUiCallback callback, @NonNull InlinePresentation presentation, int width, int height, - @Nullable IBinder hostInputToken, int displayId) { - mHandler.sendMessage( + @Nullable IBinder hostInputToken, int displayId, int userId, + int sessionId) { + mMainHandler.sendMessage( obtainMessage(InlineSuggestionRenderService::handleRenderSuggestion, InlineSuggestionRenderService.this, callback, presentation, - width, height, hostInputToken, displayId)); + width, height, hostInputToken, displayId, userId, sessionId)); } @Override public void getInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) { - mHandler.sendMessage(obtainMessage( + mMainHandler.sendMessage(obtainMessage( InlineSuggestionRenderService::handleGetInlineSuggestionsRendererInfo, InlineSuggestionRenderService.this, callback)); } + @Override + public void destroySuggestionViews(int userId, int sessionId) { + mMainHandler.sendMessage(obtainMessage( + InlineSuggestionRenderService::handleDestroySuggestionViews, + InlineSuggestionRenderService.this, userId, sessionId)); + } }.asBinder(); } diff --git a/core/java/android/service/autofill/InlineSuggestionRoot.java b/core/java/android/service/autofill/InlineSuggestionRoot.java index c879653859d8f48612037833d91e88655e29c14d..16c3f1d4e476e74b8a4bed4f90add3dadeabe192 100644 --- a/core/java/android/service/autofill/InlineSuggestionRoot.java +++ b/core/java/android/service/autofill/InlineSuggestionRoot.java @@ -58,7 +58,9 @@ public class InlineSuggestionRoot extends FrameLayout { case MotionEvent.ACTION_DOWN: { mDownX = event.getX(); mDownY = event.getY(); - } break; + } + // Intentionally fall through to the next case so that when the window is obscured + // we transfer the touch to the remote IME window and don't handle it locally. case MotionEvent.ACTION_MOVE: { final float distance = MathUtils.dist(mDownX, mDownY, diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index c2234bad3803b73d4458fb641f75623511ec83e7..95cc64ae8aab5eeff76190513df07e27f715c86c 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -564,9 +564,9 @@ public abstract class AugmentedAutofillService extends Service { } void reportResult(@Nullable List inlineSuggestionsData, - @Nullable Bundle clientState) { + @Nullable Bundle clientState, boolean showingFillWindow) { try { - mCallback.onSuccess(inlineSuggestionsData, clientState); + mCallback.onSuccess(inlineSuggestionsData, clientState, showingFillWindow); } catch (RemoteException e) { Log.e(TAG, "Error calling back with the inline suggestions data: " + e); } diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index 21738d80f2d322de0ce289db77c5d269ae7ba28f..fc3baf1c9836e4fc3c5489890c7446139a3366d5 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -56,22 +56,24 @@ public final class FillCallback { if (response == null) { mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE); - mProxy.reportResult(/* inlineSuggestionsData */ null, /* clientState */ null); + mProxy.reportResult(/* inlineSuggestionsData */ null, /* clientState */ + null, /* showingFillWindow */ false); return; } - List inlineSuggestions = response.getInlineSuggestions(); - Bundle clientState = response.getClientState(); + final List inlineSuggestions = response.getInlineSuggestions(); + final Bundle clientState = response.getClientState(); + final FillWindow fillWindow = response.getFillWindow(); + boolean showingFillWindow = false; if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) { mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE); - mProxy.reportResult(inlineSuggestions, clientState); - return; - } - - final FillWindow fillWindow = response.getFillWindow(); - if (fillWindow != null) { + } else if (fillWindow != null) { fillWindow.show(); + showingFillWindow = true; } + // We need to report result regardless of whether inline suggestions are returned or not. + mProxy.reportResult(inlineSuggestions, clientState, showingFillWindow); + // TODO(b/123099468): must notify the server so it can update the session state to avoid // showing conflicting UIs (for example, if a new request is made to the main autofill // service and it now wants to show something). diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java index 077df6cf16ef1796ed5a6f70b86e65fdc884563f..8e866466e8dfd54d73f3a4203a6e0d1df70287dc 100644 --- a/core/java/android/service/autofill/augmented/FillWindow.java +++ b/core/java/android/service/autofill/augmented/FillWindow.java @@ -208,12 +208,18 @@ public final class FillWindow implements AutoCloseable { if (sDebug) Log.d(TAG, "handleShow()"); synchronized (mLock) { if (mWm != null && mFillView != null) { - p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; - if (!mShowing) { - mWm.addView(mFillView, p); - mShowing = true; - } else { - mWm.updateViewLayout(mFillView, p); + try { + p.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; + if (!mShowing) { + mWm.addView(mFillView, p); + mShowing = true; + } else { + mWm.updateViewLayout(mFillView, p); + } + } catch (WindowManager.BadTokenException e) { + if (sDebug) Log.d(TAG, "Filed with token " + p.token + " gone."); + } catch (IllegalStateException e) { + if (sDebug) Log.d(TAG, "Exception showing window."); } } } @@ -223,8 +229,12 @@ public final class FillWindow implements AutoCloseable { if (sDebug) Log.d(TAG, "handleHide()"); synchronized (mLock) { if (mWm != null && mFillView != null && mShowing) { - mWm.removeView(mFillView); - mShowing = false; + try { + mWm.removeView(mFillView); + mShowing = false; + } catch (IllegalStateException e) { + if (sDebug) Log.d(TAG, "Exception hiding window."); + } } } } diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl index 609e382e2b9622499fd6d551b37e78877dcf4413..4dfdd4db27e53ced1b074646a7254cfd04ecb882 100644 --- a/core/java/android/service/autofill/augmented/IFillCallback.aidl +++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl @@ -30,7 +30,9 @@ import java.util.List; */ interface IFillCallback { void onCancellable(in ICancellationSignal cancellation); - void onSuccess(in @nullable List inlineSuggestionsData, in @nullable Bundle clientState); + void onSuccess(in @nullable List inlineSuggestionsData, + in @nullable Bundle clientState, + boolean showingFillWindow); boolean isCompleted(); void cancel(); } diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java index d01bc2524332bb71f4af7de08ce58f3001a08ee2..2868f1bf354713dc72eb83415a08649169e1c293 100644 --- a/core/java/android/service/controls/Control.java +++ b/core/java/android/service/controls/Control.java @@ -73,25 +73,37 @@ public final class Control implements Parcelable { }) public @interface Status {}; + /** + * Reserved for use with the {@link StatelessBuilder}, and while loading. When state is + * requested via {@link ControlsProviderService#createPublisherFor}, use other status codes + * to indicate the proper device state. + */ public static final int STATUS_UNKNOWN = 0; /** - * The device corresponding to the {@link Control} is responding correctly. + * Used to indicate that the state of the device was successfully retrieved. This includes + * all scenarios where the device may have a warning for the user, such as "Lock jammed", + * or "Vacuum stuck". Any information for the user should be set through + * {@link StatefulBuilder#setStatusText}. */ public static final int STATUS_OK = 1; /** - * The device corresponding to the {@link Control} cannot be found or was removed. + * The device corresponding to the {@link Control} cannot be found or was removed. The user + * will be alerted and directed to the application to resolve. */ public static final int STATUS_NOT_FOUND = 2; /** - * The device corresponding to the {@link Control} is in an error state. + * Used to indicate that there was a temporary error while loading the device state. A default + * error message will be displayed in place of any custom text that was set through + * {@link StatefulBuilder#setStatusText}. */ public static final int STATUS_ERROR = 3; /** - * The {@link Control} is currently disabled. + * The {@link Control} is currently disabled. A default error message will be displayed in + * place of any custom text that was set through {@link StatefulBuilder#setStatusText}. */ public static final int STATUS_DISABLED = 4; @@ -777,6 +789,13 @@ public final class Control implements Parcelable { } /** + * Set the {@link ControlTemplate} to define the primary user interaction + * + * Devices may support a variety of user interactions, and all interactions cannot be + * represented with a single {@link ControlTemplate}. Therefore, the selected template + * should be most closely aligned with what the expected primary device action will be. + * Any secondary interactions can be done via the {@link #setAppIntent(PendingIntent)}. + * * @param controlTemplate instance of {@link ControlTemplate}, that defines how the * {@link Control} will behave and what interactions are * available to the user diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java index 4e5aa0018b618cd7e7477cdf6b1ea208bc7a8487..6bd376a19fc5ea6a132ef4d20d4cf4e59eb1aacf 100644 --- a/core/java/android/service/controls/ControlsProviderService.java +++ b/core/java/android/service/controls/ControlsProviderService.java @@ -296,6 +296,10 @@ public abstract class ControlsProviderService extends Service { /** * Request SystemUI to prompt the user to add a control to favorites. + *
+ * SystemUI may not honor this request in some cases, for example if the requested + * {@link Control} is already a favorite, or the requesting package is not currently in the + * foreground. * * @param context A context * @param componentName Component name of the {@link ControlsProviderService} diff --git a/core/java/android/service/controls/templates/ControlTemplate.java b/core/java/android/service/controls/templates/ControlTemplate.java index 1e16273c455b64e5796c83792e34db09681b3680..e592fad394b84af87c6cac9bcf3818c0a428866a 100644 --- a/core/java/android/service/controls/templates/ControlTemplate.java +++ b/core/java/android/service/controls/templates/ControlTemplate.java @@ -214,10 +214,13 @@ public abstract class ControlTemplate { } /** - * Get a singleton {@link ControlTemplate} that has no features. + * Get a singleton {@link ControlTemplate}, which supports no direct user input. * - * This template has no distinctive field, not even an identifier. Used for a {@link Control} - * that accepts no type of input, or when there is no known state. + * Used by {@link Control.StatelessBuilder} when there is no known state. Can also be used + * in {@link Control.StatefulBuilder} for conveying information to a user about the + * {@link Control} but direct user interaction is not desired. Since this template has no + * corresponding {@link ControlAction}, any user interaction will launch the + * {@link Control#getAppIntent()}. * * @return a singleton {@link ControlTemplate} to indicate no specific template is used by * this {@link Control} diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 1f6555c85a667300c46c5027e6aab0a4d293c3c0..0827fef602522042992a415b0885910b5a8a82d4 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -78,6 +78,7 @@ public class ZenModeConfig implements Parcelable { private static final int DEFAULT_SOURCE = SOURCE_CONTACT; private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR; + public static final String MANUAL_RULE_ID = "MANUAL_RULE"; public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE"; public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE"; public static final List DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID, @@ -958,6 +959,48 @@ public class ZenModeConfig implements Parcelable { } }; + /** + * Converts a ZenModeConfig to a ZenPolicy + */ + public ZenPolicy toZenPolicy() { + ZenPolicy.Builder builder = new ZenPolicy.Builder() + .allowCalls(allowCalls + ? ZenModeConfig.getZenPolicySenders(allowCallsFrom) + : ZenPolicy.PEOPLE_TYPE_NONE) + .allowRepeatCallers(allowRepeatCallers) + .allowMessages(allowMessages + ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom) + : ZenPolicy.PEOPLE_TYPE_NONE) + .allowReminders(allowReminders) + .allowEvents(allowEvents) + .allowAlarms(allowAlarms) + .allowMedia(allowMedia) + .allowSystem(allowSystem) + .allowConversations(allowConversations + ? ZenModeConfig.getZenPolicySenders(allowConversationsFrom) + : ZenPolicy.PEOPLE_TYPE_NONE); + if (suppressedVisualEffects == 0) { + builder.showAllVisualEffects(); + } else { + // configs don't have an unset state: wither true or false. + builder.showFullScreenIntent( + (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) == 0); + builder.showLights( + (suppressedVisualEffects & SUPPRESSED_EFFECT_LIGHTS) == 0); + builder.showPeeking( + (suppressedVisualEffects & SUPPRESSED_EFFECT_PEEK) == 0); + builder.showStatusBarIcons( + (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_STATUS_BAR) == 0); + builder.showBadges( + (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_BADGE) == 0); + builder.showInAmbientDisplay( + (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_AMBIENT) == 0); + builder.showInNotificationList( + (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) == 0); + } + return builder.build(); + } + /** * Converts a zenPolicy to a notificationPolicy using this ZenModeConfig's values as its * defaults for all unset values in zenPolicy diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index 87295e1c95b98d5280083a21659550e6a484de4c..6d0bcffe148e55d923d6aac1d8a1ab8498065c5d 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -24,6 +24,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.proto.ProtoOutputStream; +import java.io.ByteArrayOutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -1110,6 +1111,40 @@ public final class ZenPolicy implements Parcelable { proto.end(token); } + /** + * Converts a policy to a statsd proto. + * @hides + */ + public byte[] toProto() { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ProtoOutputStream proto = new ProtoOutputStream(bytes); + + proto.write(DNDPolicyProto.CALLS, getPriorityCategoryCalls()); + proto.write(DNDPolicyProto.REPEAT_CALLERS, getPriorityCategoryRepeatCallers()); + proto.write(DNDPolicyProto.MESSAGES, getPriorityCategoryMessages()); + proto.write(DNDPolicyProto.CONVERSATIONS, getPriorityCategoryConversations()); + proto.write(DNDPolicyProto.REMINDERS, getPriorityCategoryReminders()); + proto.write(DNDPolicyProto.EVENTS, getPriorityCategoryEvents()); + proto.write(DNDPolicyProto.ALARMS, getPriorityCategoryAlarms()); + proto.write(DNDPolicyProto.MEDIA, getPriorityCategoryMedia()); + proto.write(DNDPolicyProto.SYSTEM, getPriorityCategorySystem()); + + proto.write(DNDPolicyProto.FULLSCREEN, getVisualEffectFullScreenIntent()); + proto.write(DNDPolicyProto.LIGHTS, getVisualEffectLights()); + proto.write(DNDPolicyProto.PEEK, getVisualEffectPeek()); + proto.write(DNDPolicyProto.STATUS_BAR, getVisualEffectStatusBar()); + proto.write(DNDPolicyProto.BADGE, getVisualEffectBadge()); + proto.write(DNDPolicyProto.AMBIENT, getVisualEffectAmbient()); + proto.write(DNDPolicyProto.NOTIFICATION_LIST, getVisualEffectNotificationList()); + + proto.write(DNDPolicyProto.ALLOW_CALLS_FROM, getPriorityCallSenders()); + proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM, getPriorityMessageSenders()); + proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, getPriorityConversationSenders()); + + proto.flush(); + return bytes.toByteArray(); + } + /** * Makes deep copy of this ZenPolicy. * @hide diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 4a0dd870f797aabca3a35b078aabc4606b202c5a..0341b6d96b2947d010418bc541b519d1d69ec2e1 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -1311,7 +1311,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall throw new IllegalStateException("Can't call before onCreate()"); } try { - intent.migrateExtraStreamToClipData(); + intent.migrateExtraStreamToClipData(mContext); intent.prepareToLeaveProcess(mContext); int res = mSystemService.startVoiceActivity(mToken, intent, intent.resolveType(mContext.getContentResolver()), @@ -1340,7 +1340,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall throw new IllegalStateException("Can't call before onCreate()"); } try { - intent.migrateExtraStreamToClipData(); + intent.migrateExtraStreamToClipData(mContext); intent.prepareToLeaveProcess(mContext); int res = mSystemService.startAssistantActivity(mToken, intent, intent.resolveType(mContext.getContentResolver()), diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index f944dd78dc3d62de6482b075305382a27deadd7d..0d420c5936ae8c7acb5c2ef72a9c62dbc635cfdc 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1184,7 +1184,8 @@ public abstract class WallpaperService extends Service { // may have been destroyed so now we need to make // sure it is re-created. doOffsetsChanged(false); - updateSurface(false, false, false); + // force relayout to get new surface + updateSurface(true, false, false); } onVisibilityChanged(visible); } diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index e4fbf9f0e187bc0348f4a19033670538c54371c8..4adcd6948f85cacb5f8877391eeb31b5207f262f 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -157,8 +157,8 @@ public class PhoneStateListener { * Listen for changes to the device's cell location. Note that * this will result in frequent callbacks to the listener. * {@more} - * Requires Permission: {@link android.Manifest.permission#ACCESS_COARSE_LOCATION - * ACCESS_COARSE_LOCATION} + * Requires Permission: {@link android.Manifest.permission#ACCESS_FINE_LOCATION + * ACCESS_FINE_LOCATION} *

* If you need regular location updates but want more control over * the update interval or location precision, you can set up a listener @@ -219,6 +219,9 @@ public class PhoneStateListener { /** * Listen for changes to observed cell info. * + * Listening to this event requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} + * permission. + * * @see #onCellInfoChanged */ public static final int LISTEN_CELL_INFO = 0x00000400; @@ -340,6 +343,10 @@ public class PhoneStateListener { /** * Listen for display info changed event. * + * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE + * READ_PHONE_STATE} or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges}). + * * @see #onDisplayInfoChanged */ public static final int LISTEN_DISPLAY_INFO_CHANGED = 0x00100000; @@ -457,6 +464,9 @@ public class PhoneStateListener { *

Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). * + *

Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless + * of whether the calling app has carrier privileges. + * * @see #onRegistrationFailed */ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) @@ -468,6 +478,9 @@ public class PhoneStateListener { *

Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). * + *

Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless + * of whether the calling app has carrier privileges. + * * @see #onBarringInfoChanged */ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) @@ -565,6 +578,11 @@ public class PhoneStateListener { * subId. Otherwise, this callback applies to * {@link SubscriptionManager#getDefaultSubscriptionId()}. * + * The instance of {@link ServiceState} passed as an argument here will have various levels of + * location information stripped from it depending on the location permissions that your app + * holds. Only apps holding the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission will + * receive all the information in {@link ServiceState}. + * * @see ServiceState#STATE_EMERGENCY_ONLY * @see ServiceState#STATE_IN_SERVICE * @see ServiceState#STATE_OUT_OF_SERVICE diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 8acf5fa8bdfe65b8696a26a30bf0ddbe2c8e4b91..537498c44d5e077c3a9ca50a169689cb434643bb 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -67,7 +67,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_DO_NOT_RESTORE_PRESERVED, "true"); DEFAULT_FLAGS.put("settings_tether_all_in_one", "false"); - DEFAULT_FLAGS.put("settings_contextual_home2", "true"); } /** diff --git a/core/java/android/util/apk/ApkSigningBlockUtils.java b/core/java/android/util/apk/ApkSigningBlockUtils.java index 2a4b65d23e64764e98040aa38ccc2a5750677332..6efe95cb9e92d3b5f3563765bef76d8fbb57ccb3 100644 --- a/core/java/android/util/apk/ApkSigningBlockUtils.java +++ b/core/java/android/util/apk/ApkSigningBlockUtils.java @@ -420,6 +420,7 @@ final class ApkSigningBlockUtils { static final int CONTENT_DIGEST_CHUNKED_SHA256 = 1; static final int CONTENT_DIGEST_CHUNKED_SHA512 = 2; static final int CONTENT_DIGEST_VERITY_CHUNKED_SHA256 = 3; + static final int CONTENT_DIGEST_SHA256 = 4; private static final int[] V4_CONTENT_DIGEST_ALGORITHMS = {CONTENT_DIGEST_CHUNKED_SHA512, CONTENT_DIGEST_VERITY_CHUNKED_SHA256, diff --git a/core/java/android/util/apk/SourceStampVerifier.java b/core/java/android/util/apk/SourceStampVerifier.java index a7ae32d1baa2d8ecfa8193056526146c43a9c113..5fc242353d5192845252c9146fedadf9c94ccd17 100644 --- a/core/java/android/util/apk/SourceStampVerifier.java +++ b/core/java/android/util/apk/SourceStampVerifier.java @@ -16,6 +16,7 @@ package android.util.apk; +import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_SHA256; import static android.util.apk.ApkSigningBlockUtils.compareSignatureAlgorithm; import static android.util.apk.ApkSigningBlockUtils.getLengthPrefixedSlice; import static android.util.apk.ApkSigningBlockUtils.getSignatureAlgorithmContentDigestAlgorithm; @@ -27,12 +28,10 @@ import android.util.Pair; import android.util.Slog; import android.util.jar.StrictJarFile; -import libcore.io.IoUtils; +import libcore.io.Streams; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; @@ -49,11 +48,13 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.jar.JarFile; import java.util.zip.ZipEntry; /** @@ -74,7 +75,11 @@ public abstract class SourceStampVerifier { private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; private static final int APK_SIGNATURE_SCHEME_V3_BLOCK_ID = 0xf05368c0; - private static final int SOURCE_STAMP_BLOCK_ID = 0x2b09189e; + private static final int SOURCE_STAMP_BLOCK_ID = 0x6dff800d; + + private static final int VERSION_JAR_SIGNATURE_SCHEME = 1; + private static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2; + private static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3; /** Name of the SourceStamp certificate hash ZIP entry in APKs. */ private static final String SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME = "stamp-cert-sha256"; @@ -115,7 +120,8 @@ public abstract class SourceStampVerifier { // SourceStamp present. return SourceStampVerificationResult.notPresent(); } - return verify(apk, sourceStampCertificateDigest); + byte[] manifestBytes = getManifestBytes(apkJar); + return verify(apk, sourceStampCertificateDigest, manifestBytes); } catch (IOException e) { // Any exception in reading the APK returns a non-present SourceStamp outcome // without affecting the outcome of any of the other signature schemes. @@ -126,22 +132,71 @@ public abstract class SourceStampVerifier { } private static SourceStampVerificationResult verify( - RandomAccessFile apk, byte[] sourceStampCertificateDigest) { + RandomAccessFile apk, byte[] sourceStampCertificateDigest, byte[] manifestBytes) { try { SignatureInfo signatureInfo = ApkSigningBlockUtils.findSignature(apk, SOURCE_STAMP_BLOCK_ID); - Map apkContentDigests = getApkContentDigests(apk); - return verify(signatureInfo, apkContentDigests, sourceStampCertificateDigest); - } catch (IOException | SignatureNotFoundException e) { + Map> signatureSchemeApkContentDigests = + getSignatureSchemeApkContentDigests(apk, manifestBytes); + return verify( + signatureInfo, + getSignatureSchemeDigests(signatureSchemeApkContentDigests), + sourceStampCertificateDigest); + } catch (IOException | SignatureNotFoundException | RuntimeException e) { return SourceStampVerificationResult.notVerified(); } } private static SourceStampVerificationResult verify( SignatureInfo signatureInfo, - Map apkContentDigests, + Map signatureSchemeDigests, byte[] sourceStampCertificateDigest) throws SecurityException, IOException { + ByteBuffer sourceStampBlock = signatureInfo.signatureBlock; + ByteBuffer sourceStampBlockData = + ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock); + + X509Certificate sourceStampCertificate = + verifySourceStampCertificate(sourceStampBlockData, sourceStampCertificateDigest); + + // Parse signed signature schemes block. + ByteBuffer signedSignatureSchemes = + ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlockData); + Map signedSignatureSchemeData = new HashMap<>(); + while (signedSignatureSchemes.hasRemaining()) { + ByteBuffer signedSignatureScheme = + ApkSigningBlockUtils.getLengthPrefixedSlice(signedSignatureSchemes); + int signatureSchemeId = signedSignatureScheme.getInt(); + signedSignatureSchemeData.put(signatureSchemeId, signedSignatureScheme); + } + + for (Map.Entry signatureSchemeDigest : signatureSchemeDigests.entrySet()) { + if (!signedSignatureSchemeData.containsKey(signatureSchemeDigest.getKey())) { + throw new SecurityException( + String.format( + "No signatures found for signature scheme %d", + signatureSchemeDigest.getKey())); + } + verifySourceStampSignature( + signedSignatureSchemeData.get(signatureSchemeDigest.getKey()), + sourceStampCertificate, + signatureSchemeDigest.getValue()); + } + + return SourceStampVerificationResult.verified(sourceStampCertificate); + } + + /** + * Verify the SourceStamp certificate found in the signing block is the same as the SourceStamp + * certificate found in the APK. It returns the verified certificate. + * + * @param sourceStampBlockData the source stamp block in the APK signing block which contains + * the certificate used to sign the stamp digests. + * @param sourceStampCertificateDigest the source stamp certificate digest found in the APK. + */ + private static X509Certificate verifySourceStampCertificate( + ByteBuffer sourceStampBlockData, byte[] sourceStampCertificateDigest) + throws IOException { CertificateFactory certFactory; try { certFactory = CertificateFactory.getInstance("X.509"); @@ -149,17 +204,6 @@ public abstract class SourceStampVerifier { throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); } - List> digests = - apkContentDigests.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(e -> Pair.create(e.getKey(), e.getValue())) - .collect(Collectors.toList()); - byte[] digestBytes = encodeApkContentDigests(digests); - - ByteBuffer sourceStampBlock = signatureInfo.signatureBlock; - ByteBuffer sourceStampBlockData = - ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlock); - // Parse the SourceStamp certificate. byte[] sourceStampEncodedCertificate = ApkSigningBlockUtils.readLengthPrefixedByteArray(sourceStampBlockData); @@ -172,24 +216,30 @@ public abstract class SourceStampVerifier { } catch (CertificateException e) { throw new SecurityException("Failed to decode certificate", e); } - sourceStampCertificate = - new VerbatimX509Certificate(sourceStampCertificate, sourceStampEncodedCertificate); - // Verify the SourceStamp certificate found in the signing block is the same as the - // SourceStamp certificate found in the APK. - try { - MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); - messageDigest.update(sourceStampEncodedCertificate); - byte[] sourceStampBlockCertificateDigest = messageDigest.digest(); - if (!Arrays.equals(sourceStampCertificateDigest, sourceStampBlockCertificateDigest)) { - throw new SecurityException("Certificate mismatch between APK and signature block"); - } - } catch (NoSuchAlgorithmException e) { - throw new SecurityException("Failed to find SHA-256", e); + byte[] sourceStampBlockCertificateDigest = + computeSha256Digest(sourceStampEncodedCertificate); + if (!Arrays.equals(sourceStampCertificateDigest, sourceStampBlockCertificateDigest)) { + throw new SecurityException("Certificate mismatch between APK and signature block"); } + return new VerbatimX509Certificate(sourceStampCertificate, sourceStampEncodedCertificate); + } + + /** + * Verify the SourceStamp signature found in the signing block is signed by the SourceStamp + * certificate found in the APK. + * + * @param signedBlockData the source stamp block in the APK signing block which contains the + * stamp signed digests. + * @param sourceStampCertificate the source stamp certificate used to sign the stamp digests. + * @param digest the digest to be verified being signed by the source stamp certificate. + */ + private static void verifySourceStampSignature( + ByteBuffer signedBlockData, X509Certificate sourceStampCertificate, byte[] digest) + throws IOException { // Parse the signatures block and identify supported signatures - ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(sourceStampBlockData); + ByteBuffer signatures = ApkSigningBlockUtils.getLengthPrefixedSlice(signedBlockData); int signatureCount = 0; int bestSigAlgorithm = -1; byte[] bestSigAlgorithmSignatureBytes = null; @@ -235,7 +285,7 @@ public abstract class SourceStampVerifier { if (jcaSignatureAlgorithmParams != null) { sig.setParameter(jcaSignatureAlgorithmParams); } - sig.update(digestBytes); + sig.update(digest); sigVerified = sig.verify(bestSigAlgorithmSignatureBytes); } catch (InvalidKeyException | InvalidAlgorithmParameterException @@ -247,27 +297,44 @@ public abstract class SourceStampVerifier { if (!sigVerified) { throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify"); } - - return SourceStampVerificationResult.verified(sourceStampCertificate); } - private static Map getApkContentDigests(RandomAccessFile apk) - throws IOException, SignatureNotFoundException { - // Retrieve APK content digests in V3 signing block. If a V3 signature is not found, the APK - // content digests would be re-tried from V2 signature. + private static Map> getSignatureSchemeApkContentDigests( + RandomAccessFile apk, byte[] manifestBytes) throws IOException { + Map> signatureSchemeApkContentDigests = new HashMap<>(); + + // Retrieve APK content digests in V3 signing block. try { SignatureInfo v3SignatureInfo = ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V3_BLOCK_ID); - return getApkContentDigestsFromSignatureBlock(v3SignatureInfo.signatureBlock); + signatureSchemeApkContentDigests.put( + VERSION_APK_SIGNATURE_SCHEME_V3, + getApkContentDigestsFromSignatureBlock(v3SignatureInfo.signatureBlock)); } catch (SignatureNotFoundException e) { // It's fine not to find a V3 signature. } - // Retrieve APK content digests in V2 signing block. If a V2 signature is not found, the - // process of retrieving APK content digests stops, and the stamp is considered un-verified. - SignatureInfo v2SignatureInfo = - ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID); - return getApkContentDigestsFromSignatureBlock(v2SignatureInfo.signatureBlock); + // Retrieve APK content digests in V2 signing block. + try { + SignatureInfo v2SignatureInfo = + ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID); + signatureSchemeApkContentDigests.put( + VERSION_APK_SIGNATURE_SCHEME_V2, + getApkContentDigestsFromSignatureBlock(v2SignatureInfo.signatureBlock)); + } catch (SignatureNotFoundException e) { + // It's fine not to find a V2 signature. + } + + // Retrieve manifest digest. + if (manifestBytes != null) { + Map jarSignatureSchemeApkContentDigests = new HashMap<>(); + jarSignatureSchemeApkContentDigests.put( + CONTENT_DIGEST_SHA256, computeSha256Digest(manifestBytes)); + signatureSchemeApkContentDigests.put( + VERSION_JAR_SIGNATURE_SCHEME, jarSignatureSchemeApkContentDigests); + } + + return signatureSchemeApkContentDigests; } private static Map getApkContentDigestsFromSignatureBlock( @@ -289,27 +356,45 @@ public abstract class SourceStampVerifier { return apkContentDigests; } - private static byte[] getSourceStampCertificateDigest(StrictJarFile apkJar) throws IOException { - InputStream inputStream = null; - try { - ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME); - if (zipEntry == null) { - // SourceStamp certificate hash file not found, which means that there is not - // SourceStamp present. - return null; - } - inputStream = apkJar.getInputStream(zipEntry); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + private static Map getSignatureSchemeDigests( + Map> signatureSchemeApkContentDigests) { + Map digests = new HashMap<>(); + for (Map.Entry> signatureSchemeApkContentDigest : + signatureSchemeApkContentDigests.entrySet()) { + List> apkDigests = + getApkDigests(signatureSchemeApkContentDigest.getValue()); + digests.put( + signatureSchemeApkContentDigest.getKey(), encodeApkContentDigests(apkDigests)); + } + return digests; + } - // Trying to read the certificate digest, which should be less than 1024 bytes. - byte[] buffer = new byte[1024]; - int count = inputStream.read(buffer, 0, buffer.length); - byteArrayOutputStream.write(buffer, 0, count); + private static List> getApkDigests( + Map apkContentDigests) { + List> digests = new ArrayList<>(); + for (Map.Entry apkContentDigest : apkContentDigests.entrySet()) { + digests.add(Pair.create(apkContentDigest.getKey(), apkContentDigest.getValue())); + } + digests.sort(Comparator.comparing(pair -> pair.first)); + return digests; + } - return byteArrayOutputStream.toByteArray(); - } finally { - IoUtils.closeQuietly(inputStream); + private static byte[] getSourceStampCertificateDigest(StrictJarFile apkJar) throws IOException { + ZipEntry zipEntry = apkJar.findEntry(SOURCE_STAMP_CERTIFICATE_HASH_ZIP_ENTRY_NAME); + if (zipEntry == null) { + // SourceStamp certificate hash file not found, which means that there is not + // SourceStamp present. + return null; + } + return Streams.readFully(apkJar.getInputStream(zipEntry)); + } + + private static byte[] getManifestBytes(StrictJarFile apkJar) throws IOException { + ZipEntry zipEntry = apkJar.findEntry(JarFile.MANIFEST_NAME); + if (zipEntry == null) { + return null; } + return Streams.readFully(apkJar.getInputStream(zipEntry)); } private static byte[] encodeApkContentDigests(List> apkContentDigests) { @@ -329,6 +414,16 @@ public abstract class SourceStampVerifier { return result.array(); } + private static byte[] computeSha256Digest(byte[] input) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + messageDigest.update(input); + return messageDigest.digest(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Failed to find SHA-256", e); + } + } + private static void closeApkJar(StrictJarFile apkJar) { try { if (apkJar == null) { diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING new file mode 100644 index 0000000000000000000000000000000000000000..8544e82e04e030f253a0630b71274162d2410b4a --- /dev/null +++ b/core/java/android/util/apk/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksCoreTests", + "options": [ + { + "include-filter": "android.util.apk.SourceStampVerifierTest" + } + ] + } + ] +} diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 8db1703a627fd9ab2a1244aa46730b1542941779..0cc469a2d5eb06c84e8e8706f3dce05e5eb8e48d 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -241,12 +241,25 @@ public final class Display { * This flag identifies secondary displays that should show system decorations, such as status * bar, navigation bar, home activity or IME. *

+ *

Note that this flag doesn't work without {@link #FLAG_TRUSTED}

* + * @see #getFlags() * @hide */ // TODO (b/114338689): Remove the flag and use IWindowManager#setShouldShowSystemDecors public static final int FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 6; + /** + * Flag: The display is trusted to show system decorations and receive inputs without users' + * touch. + * @see #FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS + * + * @see #getFlags() + * @hide + */ + @TestApi + public static final int FLAG_TRUSTED = 1 << 7; + /** * Display flag: Indicates that the contents of the display should not be scaled * to fit the physical screen dimensions. Used for development only to emulate @@ -564,6 +577,7 @@ public final class Display { * @see #FLAG_SUPPORTS_PROTECTED_BUFFERS * @see #FLAG_SECURE * @see #FLAG_PRIVATE + * @see #FLAG_ROUND */ public int getFlags() { return mFlags; @@ -1222,6 +1236,16 @@ public final class Display { Display.FLAG_PRESENTATION; } + /** + * @return {@code true} if the display is a trusted display. + * + * @see #FLAG_TRUSTED + * @hide + */ + public boolean isTrusted() { + return (mFlags & FLAG_TRUSTED) == FLAG_TRUSTED; + } + private void updateDisplayInfoLocked() { // Note: The display manager caches display info objects on our behalf. DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId); diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index d369883f3ac3ffbc0de1b3e0b51d726dbfd9d492..b1ede4102bec0ff2c2e2c6ff5b2a03a51eba5b30 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -717,6 +717,15 @@ public final class DisplayInfo implements Parcelable { if ((flags & Display.FLAG_ROUND) != 0) { result.append(", FLAG_ROUND"); } + if ((flags & Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) { + result.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD"); + } + if ((flags & Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) { + result.append(", FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS"); + } + if ((flags & Display.FLAG_TRUSTED) != 0) { + result.append(", FLAG_TRUSTED"); + } return result.toString(); } } diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index f6c72c4eefbc9bf331413ee0306d2f42441ed770..55c527ba6fa6876a813bc32b939e7e67bc535b7a 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -16,6 +16,8 @@ package android.view; +import static android.os.StrictMode.vmIncorrectContextUseEnabled; + import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; @@ -26,9 +28,12 @@ import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFI import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.StrictMode; import android.os.SystemClock; +import android.util.Log; import com.android.internal.util.FrameworkStatsLog; @@ -228,6 +233,7 @@ public class GestureDetector { } } + private static final String TAG = GestureDetector.class.getSimpleName(); @UnsupportedAppUsage private int mTouchSlopSquare; private int mDoubleTapTouchSlopSquare; @@ -378,7 +384,8 @@ public class GestureDetector { * You may only use this constructor from a {@link android.os.Looper} thread. * @see android.os.Handler#Handler() * - * @param context the application's context + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. If the listener implements the {@link OnDoubleTapListener} or * {@link OnContextClickListener} then it will also be set as the listener for @@ -395,7 +402,8 @@ public class GestureDetector { * thread associated with the supplied {@link android.os.Handler}. * @see android.os.Handler#Handler() * - * @param context the application's context + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. If the listener implements the {@link OnDoubleTapListener} or * {@link OnContextClickListener} then it will also be set as the listener for @@ -425,7 +433,8 @@ public class GestureDetector { * thread associated with the supplied {@link android.os.Handler}. * @see android.os.Handler#Handler() * - * @param context the application's context + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. * @param handler the handler to use for running deferred listener events. @@ -456,6 +465,17 @@ public class GestureDetector { mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); mAmbiguousGestureMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier(); } else { + if (!context.isUiContext() && vmIncorrectContextUseEnabled()) { + final String errorMessage = + "Tried to access UI constants from a non-visual Context."; + final String message = "GestureDetector must be accessed from Activity or other " + + "visual Context. Use an Activity or a Context created with " + + "Context#createWindowContext(int, Bundle), which are adjusted to the " + + "configuration and visual bounds of an area on screen."; + final Exception exception = new IllegalArgumentException(errorMessage); + StrictMode.onIncorrectContextUsed(message, exception); + Log.e(TAG, errorMessage + message, exception); + } final ViewConfiguration configuration = ViewConfiguration.get(context); touchSlop = configuration.getScaledTouchSlop(); doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop(); diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index a4800726bbe8bb50fa2029dbe2dca90097e590e7..ad43f9556d8d7fe852df3e8c27c3e0f26c6e3107 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -125,6 +125,13 @@ public final class ImeFocusController { final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView; onViewFocusChanged(viewForWindowFocus, true); + // Starting new input when the next focused view is same as served view but the + // editor is not aligned with the same editor or editor is inactive. + final boolean nextFocusIsServedView = mServedView != null && mServedView == focusedView; + if (nextFocusIsServedView && !immDelegate.isSameEditorAndAcceptingText(focusedView)) { + forceFocus = true; + } + immDelegate.startInputAsyncOnWindowFocusGain(viewForWindowFocus, windowAttribute.softInputMode, windowAttribute.flags, forceFocus); } @@ -247,6 +254,7 @@ public final class ImeFocusController { void setCurrentRootView(ViewRootImpl rootView); boolean isCurrentRootView(ViewRootImpl rootView); boolean isRestartOnNextWindowFocus(boolean reset); + boolean isSameEditorAndAcceptingText(View view); } public View getServedView() { diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 8b5af29517cb5ecf2f400b7e5f24cb21c306e2a7..c1998c6009cf85c027e0310f12831ad87d97b793 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -21,6 +21,7 @@ import static android.view.InsetsState.ITYPE_IME; import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; +import android.os.IBinder; import android.os.Parcel; import android.text.TextUtils; import android.view.SurfaceControl.Transaction; @@ -118,11 +119,11 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching // this code here means that we now got control, so we can start the animation immediately. // If client window is trying to control IME and IME is already visible, it is immediate. - if (fromIme || mState.getSource(getType()).isVisible()) { + if (fromIme || mState.getSource(getType()).isVisible() && getControl() != null) { return ShowResult.SHOW_IMMEDIATELY; } - return getImm().requestImeShow(null /* resultReceiver */) + return getImm().requestImeShow(mController.getHost().getWindowToken()) ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED; } @@ -131,12 +132,15 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { */ @Override void notifyHidden() { - getImm().notifyImeHidden(); + getImm().notifyImeHidden(mController.getHost().getWindowToken()); } @Override public void removeSurface() { - getImm().removeImeSurface(); + final IBinder window = mController.getHost().getWindowToken(); + if (window != null) { + getImm().removeImeSurface(window); + } } @Override @@ -145,6 +149,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { super.setControl(control, showTypes, hideTypes); if (control == null && !mIsRequestedVisibleAwaitingControl) { hide(); + removeSurface(); } } @@ -153,6 +158,15 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { return mIsRequestedVisibleAwaitingControl || isRequestedVisible(); } + @Override + public void onPerceptible(boolean perceptible) { + super.onPerceptible(perceptible); + final IBinder window = mController.getHost().getWindowToken(); + if (window != null) { + getImm().reportPerceptible(window, perceptible); + } + } + private boolean isDummyOrEmptyEditor(EditorInfo info) { // TODO(b/123044812): Handle dummy input gracefully in IME Insets API return info == null || (info.fieldId <= 0 && info.inputType <= 0); diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 74c186948b2f2bd878ac7b8965cc516d14454393..3431c3ecc310f166ddcba675397e94ff4723fc85 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -16,6 +16,7 @@ package android.view; +import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; /** @@ -64,4 +65,15 @@ public interface InsetsAnimationControlCallbacks { * previous calls to applySurfaceParams. */ void releaseSurfaceControlFromRt(SurfaceControl sc); + + /** + * Reports that the perceptibility of the given types has changed to the given value. + * + * A type is perceptible if it is not (almost) entirely off-screen and not (almost) entirely + * transparent. + * + * @param types the (public) types whose perceptibility has changed + * @param perceptible true, if the types are now perceptible, false if they are not perceptible + */ + void reportPerceptible(@InsetsType int types, boolean perceptible); } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index cd56ca9251abea2bfc90834790b810e3498984e8..31da83ad5137ebbfef97353ac4d8d57a9671cb98 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -16,13 +16,14 @@ package android.view; +import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.DEBUG; import static android.view.InsetsState.ISIDE_BOTTOM; -import static android.view.InsetsState.ISIDE_FLOATING; import static android.view.InsetsState.ISIDE_LEFT; import static android.view.InsetsState.ISIDE_RIGHT; import static android.view.InsetsState.ISIDE_TOP; +import static android.view.InsetsState.ITYPE_IME; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; @@ -74,6 +75,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll private final @InsetsType int mTypes; private final InsetsAnimationControlCallbacks mController; private final WindowInsetsAnimation mAnimation; + /** @see WindowInsetsAnimationController#hasZeroInsetsIme */ + private final boolean mHasZeroInsetsIme; private Insets mCurrentInsets; private Insets mPendingInsets; private float mPendingFraction; @@ -84,6 +87,7 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll private float mPendingAlpha = 1.0f; @VisibleForTesting(visibility = PACKAGE) public boolean mReadyDispatched; + private Boolean mPerceptible; @VisibleForTesting public InsetsAnimationControlImpl(SparseArray controls, Rect frame, @@ -102,6 +106,12 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll null /* typeSideMap */); mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */, mTypeSideMap); + mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsInternalType(ITYPE_IME); + if (mHasZeroInsetsIme) { + // IME has shownInsets of ZERO, and can't map to a side by default. + // Map zero insets IME to bottom, making it a special case of bottom insets. + mTypeSideMap.put(ITYPE_IME, ISIDE_BOTTOM); + } buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls); mAnimation = new WindowInsetsAnimation(mTypes, interpolator, @@ -112,6 +122,19 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll new Bounds(mHiddenInsets, mShownInsets)); } + private boolean calculatePerceptible(Insets currentInsets, float currentAlpha) { + return 100 * currentInsets.left >= 5 * (mShownInsets.left - mHiddenInsets.left) + && 100 * currentInsets.top >= 5 * (mShownInsets.top - mHiddenInsets.top) + && 100 * currentInsets.right >= 5 * (mShownInsets.right - mHiddenInsets.right) + && 100 * currentInsets.bottom >= 5 * (mShownInsets.bottom - mHiddenInsets.bottom) + && currentAlpha >= 0.5f; + } + + @Override + public boolean hasZeroInsetsIme() { + return mHasZeroInsetsIme; + } + @Override public Insets getHiddenStateInsets() { return mHiddenInsets; @@ -161,6 +184,11 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mPendingInsets = sanitize(insets); mPendingAlpha = sanitize(alpha); mController.scheduleApplyChangeInsets(this); + boolean perceptible = calculatePerceptible(mPendingInsets, mPendingAlpha); + if (mPerceptible == null || perceptible != mPerceptible) { + mController.reportPerceptible(mTypes, perceptible); + mPerceptible = perceptible; + } } @VisibleForTesting @@ -182,8 +210,6 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll params, state, mPendingAlpha); updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, mShownInsets.bottom, mPendingInsets.bottom, params, state, mPendingAlpha); - updateLeashesForSide(ISIDE_FLOATING, 0 /* offset */, 0 /* inset */, 0 /* maxInset */, - params, state, mPendingAlpha); mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()])); mCurrentInsets = mPendingInsets; @@ -290,6 +316,9 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll if (insets == null) { insets = getCurrentInsets(); } + if (hasZeroInsetsIme()) { + return insets; + } return Insets.max(Insets.min(insets, mShownInsets), mHiddenInsets); } @@ -313,17 +342,19 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mTmpFrame.set(source.getFrame()); addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame); - state.getSource(source.getType()).setVisible(side == ISIDE_FLOATING || inset != 0); + final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM + ? (mAnimationType == ANIMATION_TYPE_SHOW ? true : !mFinished) + : inset != 0; + + state.getSource(source.getType()).setVisible(visible); state.getSource(source.getType()).setFrame(mTmpFrame); // If the system is controlling the insets source, the leash can be null. if (leash != null) { SurfaceParams params = new SurfaceParams.Builder(leash) - .withAlpha(side == ISIDE_FLOATING ? 1 : alpha) + .withAlpha(alpha) .withMatrix(mTmpMatrix) - .withVisibility(side == ISIDE_FLOATING - ? mShownOnFinish - : inset != 0 /* visible */) + .withVisibility(visible) .build(); surfaceParams.add(params); } diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 0e71b7643b7d9bb1b599fed15af18a642d5d1a15..123604489da498468c5e5ce9997eedc1d3dfa597 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -90,6 +90,11 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro // Since we don't push the SurfaceParams to the RT we can release directly sc.release(); } + + @Override + public void reportPerceptible(int types, boolean perceptible) { + mMainThreadHandler.post(() -> mOuterCallbacks.reportPerceptible(types, perceptible)); + } }; @UiThread diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index ef9edc6c074171ea96f809706a19c30bc352d690..a679b3740fd90ab177dfc2564faca30d880a597f 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -35,6 +35,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.CancellationSignal; import android.os.Handler; +import android.os.IBinder; import android.os.Trace; import android.util.ArraySet; import android.util.Log; @@ -162,6 +163,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation */ @Nullable String getRootViewTitle(); + + /** @see ViewRootImpl#dipToPx */ + int dipToPx(int dips); + + /** + * @return token associated with the host, if it has one. + */ + @Nullable + IBinder getWindowToken(); } private static final String TAG = "InsetsController"; @@ -254,12 +264,17 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public static class InternalAnimationControlListener implements WindowInsetsAnimationControlListener { + /** The amount IME will move up/down when animating in floating mode. */ + protected static final int FLOATING_IME_BOTTOM_INSET = -80; + private WindowInsetsAnimationController mController; private ValueAnimator mAnimator; private final boolean mShow; private final boolean mHasAnimationCallbacks; private final @InsetsType int mRequestedTypes; private final long mDurationMs; + private final boolean mDisable; + private final int mFloatingImeBottomInset; private ThreadLocal mSfAnimationHandlerThreadLocal = new ThreadLocal() { @@ -272,11 +287,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation }; public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks, - int requestedTypes) { + int requestedTypes, boolean disable, int floatingImeBottomInset) { mShow = show; mHasAnimationCallbacks = hasAnimationCallbacks; mRequestedTypes = requestedTypes; mDurationMs = calculateDurationMs(); + mDisable = disable; + mFloatingImeBottomInset = floatingImeBottomInset; } @Override @@ -284,15 +301,26 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mController = controller; if (DEBUG) Log.d(TAG, "default animation onReady types: " + types); + if (mDisable) { + onAnimationFinish(); + return; + } mAnimator = ValueAnimator.ofFloat(0f, 1f); mAnimator.setDuration(mDurationMs); mAnimator.setInterpolator(new LinearInterpolator()); + Insets hiddenInsets = controller.getHiddenStateInsets(); + // IME with zero insets is a special case: it will animate-in from offscreen and end + // with final insets of zero and vice-versa. + hiddenInsets = controller.hasZeroInsetsIme() + ? Insets.of(hiddenInsets.left, hiddenInsets.top, hiddenInsets.right, + mFloatingImeBottomInset) + : hiddenInsets; Insets start = mShow - ? controller.getHiddenStateInsets() + ? hiddenInsets : controller.getShownStateInsets(); Insets end = mShow ? controller.getShownStateInsets() - : controller.getHiddenStateInsets(); + : hiddenInsets; Interpolator insetsInterpolator = getInterpolator(); Interpolator alphaInterpolator = getAlphaInterpolator(); mAnimator.addUpdateListener(animation -> { @@ -477,6 +505,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private DisplayCutout mLastDisplayCutout; private boolean mStartingAnimation; private int mCaptionInsetsHeight = 0; + private boolean mAnimationsDisabled; private Runnable mPendingControlTimeout = this::abortPendingImeControlRequest; private final ArrayList mControllableInsetsChangedListeners @@ -485,6 +514,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation /** Set of inset types for which an animation was started since last resetting this field */ private @InsetsType int mLastStartedAnimTypes; + /** Set of inset types which cannot be controlled by the user animation */ + private @InsetsType int mDisabledUserAnimationInsetsTypes; + + private Runnable mInvokeControllableInsetsChangedListeners = + this::invokeControllableInsetsChangedListeners; + public InsetsController(Host host) { this(host, (controller, type) -> { if (type == ITYPE_IME) { @@ -575,21 +610,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public boolean onStateChanged(InsetsState state) { - boolean localStateChanged = !mState.equals(state, true /* excludingCaptionInsets */) + boolean stateChanged = !mState.equals(state, true /* excludingCaptionInsets */, + false /* excludeInvisibleIme */) || !captionInsetsUnchanged(); - if (!localStateChanged && mLastDispatchedState.equals(state)) { + if (!stateChanged && mLastDispatchedState.equals(state)) { return false; } if (DEBUG) Log.d(TAG, "onStateChanged: " + state); updateState(state); + + boolean localStateChanged = !mState.equals(mLastDispatchedState, + true /* excludingCaptionInsets */, true /* excludeInvisibleIme */); mLastDispatchedState.set(state, true /* copySources */); + applyLocalVisibilityOverride(); if (localStateChanged) { - if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged"); + if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged, send state to WM: " + mState); mHost.notifyInsetsChanged(); - } - if (!mState.equals(mLastDispatchedState, true /* excludingCaptionInsets */)) { - if (DEBUG) Log.d(TAG, "onStateChanged, send state to WM: " + mState); updateRequestedState(); } return true; @@ -597,20 +634,57 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void updateState(InsetsState newState) { mState.setDisplayFrame(newState.getDisplayFrame()); - for (int i = newState.getSourcesCount() - 1; i >= 0; i--) { - InsetsSource source = newState.sourceAt(i); - getSourceConsumer(source.getType()).updateSource(source); + @InsetsType int disabledUserAnimationTypes = 0; + @InsetsType int[] cancelledUserAnimationTypes = {0}; + for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { + InsetsSource source = newState.peekSource(type); + if (source == null) continue; + @AnimationType int animationType = getAnimationType(type); + if (!source.isUserControllable()) { + @InsetsType int insetsType = toPublicType(type); + // The user animation is not allowed when visible frame is empty. + disabledUserAnimationTypes |= insetsType; + if (animationType == ANIMATION_TYPE_USER) { + // Existing user animation needs to be cancelled. + animationType = ANIMATION_TYPE_NONE; + cancelledUserAnimationTypes[0] |= insetsType; + } + } + getSourceConsumer(type).updateSource(source, animationType); } - for (int i = mState.getSourcesCount() - 1; i >= 0; i--) { - InsetsSource source = mState.sourceAt(i); - if (newState.peekSource(source.getType()) == null) { - mState.removeSource(source.getType()); + for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { + InsetsSource source = mState.peekSource(type); + if (source == null) continue; + if (newState.peekSource(type) == null) { + mState.removeSource(type); } } if (mCaptionInsetsHeight != 0) { mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top, mFrame.right, mFrame.top + mCaptionInsetsHeight)); } + + updateDisabledUserAnimationTypes(disabledUserAnimationTypes); + + if (cancelledUserAnimationTypes[0] != 0) { + mHandler.post(() -> show(cancelledUserAnimationTypes[0])); + } + } + + private void updateDisabledUserAnimationTypes(@InsetsType int disabledUserAnimationTypes) { + @InsetsType int diff = mDisabledUserAnimationInsetsTypes ^ disabledUserAnimationTypes; + if (diff != 0) { + for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { + InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); + if (consumer.getControl() != null + && (toPublicType(consumer.getType()) & diff) != 0) { + mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners); + mHandler.post(mInvokeControllableInsetsChangedListeners); + break; + } + } + mDisabledUserAnimationInsetsTypes = disabledUserAnimationTypes; + } } private boolean captionInsetsUnchanged() { @@ -663,7 +737,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } - final boolean hasControl = mTmpControlArray.size() > 0; + boolean requestedStateStale = false; final int[] showTypes = new int[1]; final int[] hideTypes = new int[1]; @@ -680,9 +754,26 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // Ensure to create source consumers if not available yet. for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { final InsetsSourceControl control = mTmpControlArray.valueAt(i); - InsetsSourceConsumer consumer = getSourceConsumer(control.getType()); + final @InternalInsetsType int type = control.getType(); + final InsetsSourceConsumer consumer = getSourceConsumer(type); consumer.setControl(control, showTypes, hideTypes); + if (!requestedStateStale) { + final boolean requestedVisible = consumer.isRequestedVisible(); + + // We might have changed our requested visibilities while we don't have the control, + // so we need to update our requested state once we have control. Otherwise, our + // requested state at the server side might be incorrect. + final boolean requestedVisibilityChanged = + requestedVisible != mRequestedState.getSourceOrDefaultVisibility(type); + + // The IME client visibility will be reset by insets source provider while updating + // control, so if IME is requested visible, we need to send the request to server. + final boolean imeRequestedVisible = type == ITYPE_IME && requestedVisible; + + requestedStateStale = requestedVisibilityChanged || imeRequestedVisible; + } + } mTmpControlArray.clear(); @@ -698,10 +789,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (hideTypes[0] != 0) { applyAnimation(hideTypes[0], false /* show */, false /* fromIme */); } - if (hasControl && mRequestedState.getSourcesCount() > 0) { - // We might have changed our requested visibilities while we don't have the control, - // so we need to update our requested state once we have control. Otherwise, our - // requested state at the server side might be incorrect. + if (requestedStateStale) { updateRequestedState(); } } @@ -791,7 +879,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs, @Nullable Interpolator interpolator, @AnimationType int animationType) { - if (!checkDisplayFramesForControlling()) { + if ((mState.calculateUncontrollableInsetsFromFrame(mFrame) & types) != 0) { listener.onCancelled(null); return; } @@ -801,13 +889,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation false /* useInsetsAnimationThread */); } - private boolean checkDisplayFramesForControlling() { - - // If the frame of our window doesn't span the entire display, the control API makes very - // little sense, as we don't deal with negative insets. So just cancel immediately. - return mState.getDisplayFrame().equals(mFrame); - } - private void controlAnimationUnchecked(@InsetsType int types, @Nullable CancellationSignal cancellationSignal, WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme, @@ -821,6 +902,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation + " while an existing " + Type.toString(mTypesBeingCancelled) + " is being cancelled."); } + if (animationType == ANIMATION_TYPE_USER) { + final @InsetsType int disabledTypes = types & mDisabledUserAnimationInsetsTypes; + if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes); + types &= ~mDisabledUserAnimationInsetsTypes; + + if (fromIme && (disabledTypes & ime()) != 0 + && !mState.getSource(ITYPE_IME).isVisible()) { + // We've requested IMM to show IME, but the IME is not controllable. We need to + // cancel the request. + getSourceConsumer(ITYPE_IME).hide(true, animationType); + } + } if (types == 0) { // nothing to animate. listener.onCancelled(null); @@ -1168,8 +1261,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks(); - final InternalAnimationControlListener listener = - new InternalAnimationControlListener(show, hasAnimationCallbacks, types); + final InternalAnimationControlListener listener = new InternalAnimationControlListener( + show, hasAnimationCallbacks, types, mAnimationsDisabled, + mHost.dipToPx(InternalAnimationControlListener.FLOATING_IME_BOTTOM_INSET)); // Show/hide animations always need to be relative to the display frame, in order that shown // and hidden state insets are correct. @@ -1284,24 +1378,28 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return mHost.getSystemBarsBehavior(); } + @Override + public void setAnimationsDisabled(boolean disable) { + mAnimationsDisabled = disable; + } + private @InsetsType int calculateControllableTypes() { - if (!checkDisplayFramesForControlling()) { - return 0; - } @InsetsType int result = 0; for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); - if (consumer.getControl() != null) { + InsetsSource source = mState.peekSource(consumer.mType); + if (consumer.getControl() != null && source != null && source.isUserControllable()) { result |= toPublicType(consumer.mType); } } - return result; + return result & ~mState.calculateUncontrollableInsetsFromFrame(mFrame); } /** * @return The types that are now animating due to a listener invoking control/show/hide */ private @InsetsType int invokeControllableInsetsChangedListeners() { + mHandler.removeCallbacks(mInvokeControllableInsetsChangedListeners); mLastStartedAnimTypes = 0; @InsetsType int types = calculateControllableTypes(); int size = mControllableInsetsChangedListeners.size(); @@ -1331,6 +1429,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mHost.releaseSurfaceControlFromRt(sc); } + @Override + public void reportPerceptible(int types, boolean perceptible) { + final ArraySet internalTypes = toInternalType(types); + final int size = mSourceConsumers.size(); + for (int i = 0; i < size; i++) { + final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); + if (internalTypes.contains(consumer.getType())) { + consumer.onPerceptible(perceptible); + } + } + } + Host getHost() { return mHost; } diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index b0158467a17b399526ea32406bcea5d22b7dde89..dbf75705c073fd949c7fd706112c61e63dff7ba5 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -92,6 +92,11 @@ public class InsetsSource implements Parcelable { return mVisible; } + boolean isUserControllable() { + // If mVisibleFrame is null, it will be the same area as mFrame. + return mVisibleFrame == null || !mVisibleFrame.isEmpty(); + } + /** * Calculates the insets this source will cause to a client window. * @@ -191,6 +196,14 @@ public class InsetsSource implements Parcelable { @Override public boolean equals(Object o) { + return equals(o, false); + } + + /** + * @param excludeInvisibleImeFrames If {@link InsetsState#ITYPE_IME} frames should be ignored + * when IME is not visible. + */ + public boolean equals(Object o, boolean excludeInvisibleImeFrames) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -198,6 +211,7 @@ public class InsetsSource implements Parcelable { if (mType != that.mType) return false; if (mVisible != that.mVisible) return false; + if (excludeInvisibleImeFrames && !mVisible && mType == ITYPE_IME) return true; if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; return mFrame.equals(that.mFrame); } diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 3869484468ae937ae8cb858e19571a571e10084a..700dc66fab556becedf5277a9d6bd570dac27ebd 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -18,10 +18,12 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.AnimationType; -import static android.view.InsetsState.getDefaultVisibility; import static android.view.InsetsController.DEBUG; +import static android.view.InsetsState.getDefaultVisibility; import static android.view.InsetsState.toPublicType; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.Rect; @@ -151,6 +153,9 @@ public class InsetsSourceConsumer { if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) { applyHiddenToControl(); } + if (!requestedVisible && !mIsAnimationPending) { + removeSurface(); + } } } if (lastControl != null) { @@ -219,9 +224,10 @@ public class InsetsSourceConsumer { final boolean hasControl = mSourceControl != null; // We still need to let the legacy app know the visibility change even if we don't have the - // control. + // control. If we don't have the source, we don't change the requested visibility for making + // the callback behavior compatible. mController.updateCompatSysUiVisibility( - mType, hasControl ? mRequestedVisible : isVisible, hasControl); + mType, (hasControl || source == null) ? mRequestedVisible : isVisible, hasControl); // If we don't have control, we are not able to change the visibility. if (!hasControl) { @@ -257,6 +263,15 @@ public class InsetsSourceConsumer { return ShowResult.SHOW_IMMEDIATELY; } + /** + * Reports that this source's perceptibility has changed + * + * @param perceptible true if the source is perceptible, false otherwise. + * @see InsetsAnimationControlCallbacks#reportPerceptible + */ + public void onPerceptible(boolean perceptible) { + } + /** * Notify listeners that window is now hidden. */ @@ -271,16 +286,19 @@ public class InsetsSourceConsumer { // no-op for types that always return ShowResult#SHOW_IMMEDIATELY. } - void updateSource(InsetsSource newSource) { + @VisibleForTesting(visibility = PACKAGE) + public void updateSource(InsetsSource newSource, @AnimationType int animationType) { InsetsSource source = mState.peekSource(mType); - if (source == null || mController.getAnimationType(mType) == ANIMATION_TYPE_NONE + if (source == null || animationType == ANIMATION_TYPE_NONE || source.getFrame().equals(newSource.getFrame())) { + mPendingFrame = null; + mPendingVisibleFrame = null; mState.addSource(newSource); return; } // Frame is changing while animating. Keep note of the new frame but keep existing frame - // until animaition is finished. + // until animation is finished. newSource = new InsetsSource(newSource); mPendingFrame = new Rect(newSource.getFrame()); mPendingVisibleFrame = newSource.getVisibleFrame() != null @@ -292,7 +310,8 @@ public class InsetsSourceConsumer { if (DEBUG) Log.d(TAG, "updateSource: " + newSource); } - boolean notifyAnimationFinished() { + @VisibleForTesting(visibility = PACKAGE) + public boolean notifyAnimationFinished() { if (mPendingFrame != null) { InsetsSource source = mState.getSource(mType); source.setFrame(mPendingFrame); @@ -332,5 +351,6 @@ public class InsetsSourceConsumer { t.hide(mSourceControl.getLeash()); } t.apply(); + onPerceptible(mRequestedVisible); } } diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 2c2ecd504519b0c120e818f4aa1065349ef322a6..51b49214387a01ff99311d0520d9a86fb23b1907 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -45,7 +45,7 @@ public class InsetsSourceControl implements Parcelable { public InsetsSourceControl(InsetsSourceControl other) { mType = other.mType; if (other.mLeash != null) { - mLeash = new SurfaceControl(other.mLeash); + mLeash = new SurfaceControl(other.mLeash, "InsetsSourceControl"); } else { mLeash = null; } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 3822ee59b4aad71f0870383886ba9e302796a481..91e7591193f17e618db43a9dd1c3e4107bf7cde0 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -50,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; import java.util.Objects; import java.util.StringJoiner; @@ -117,6 +118,7 @@ public class InsetsState implements Parcelable { public static final int ITYPE_EXTRA_NAVIGATION_BAR = 15; static final int LAST_TYPE = ITYPE_EXTRA_NAVIGATION_BAR; + public static final int SIZE = LAST_TYPE + 1; // Derived types @@ -140,7 +142,7 @@ public class InsetsState implements Parcelable { static final int ISIDE_FLOATING = 4; static final int ISIDE_UNKNOWN = 5; - private final ArrayMap mSources = new ArrayMap<>(); + private InsetsSource[] mSources = new InsetsSource[SIZE]; /** * The frame of the display these sources are relative to. @@ -177,7 +179,7 @@ public class InsetsState implements Parcelable { final Rect relativeFrame = new Rect(frame); final Rect relativeFrameMax = new Rect(frame); for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { - InsetsSource source = mSources.get(type); + InsetsSource source = mSources[type]; if (source == null) { int index = indexOf(toPublicType(type)); if (typeInsetsMap[index] == null) { @@ -227,7 +229,7 @@ public class InsetsState implements Parcelable { public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) { Insets insets = Insets.NONE; for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { - InsetsSource source = mSources.get(type); + InsetsSource source = mSources[type]; if (source == null) { continue; } @@ -245,6 +247,44 @@ public class InsetsState implements Parcelable { return insets.toRect(); } + /** + * Calculate which insets *cannot* be controlled, because the frame does not cover the + * respective side of the inset. + * + * If the frame of our window doesn't cover the entire inset, the control API makes very + * little sense, as we don't deal with negative insets. + */ + @InsetsType + public int calculateUncontrollableInsetsFromFrame(Rect frame) { + int blocked = 0; + for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { + InsetsSource source = mSources[type]; + if (source == null) { + continue; + } + if (!canControlSide(frame, getInsetSide( + source.calculateInsets(frame, true /* ignoreVisibility */)))) { + blocked |= toPublicType(type); + } + } + return blocked; + } + + private boolean canControlSide(Rect frame, int side) { + switch (side) { + case ISIDE_LEFT: + case ISIDE_RIGHT: + return frame.left == mDisplayFrame.left && frame.right == mDisplayFrame.right; + case ISIDE_TOP: + case ISIDE_BOTTOM: + return frame.top == mDisplayFrame.top && frame.bottom == mDisplayFrame.bottom; + case ISIDE_FLOATING: + return true; + default: + return false; + } + } + private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility, Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap, @Nullable boolean[] typeVisibilityMap) { @@ -312,11 +352,39 @@ public class InsetsState implements Parcelable { } public InsetsSource getSource(@InternalInsetsType int type) { - return mSources.computeIfAbsent(type, InsetsSource::new); + InsetsSource source = mSources[type]; + if (source != null) { + return source; + } + source = new InsetsSource(type); + mSources[type] = source; + return source; } public @Nullable InsetsSource peekSource(@InternalInsetsType int type) { - return mSources.get(type); + return mSources[type]; + } + + public boolean hasSources() { + for (int i = 0; i < SIZE; i++) { + if (mSources[i] != null) { + return true; + } + } + return false; + } + + /** + * Returns the source visibility or the default visibility if the source doesn't exist. This is + * useful if when treating this object as a request. + * + * @param type The {@link InternalInsetsType} to query. + * @return {@code true} if the source is visible or the type is default visible and the source + * doesn't exist. + */ + public boolean getSourceOrDefaultVisibility(@InternalInsetsType int type) { + final InsetsSource source = mSources[type]; + return source != null ? source.isVisible() : getDefaultVisibility(type); } public void setDisplayFrame(Rect frame) { @@ -334,7 +402,7 @@ public class InsetsState implements Parcelable { * @param type The {@link InternalInsetsType} of the source to remove */ public void removeSource(@InternalInsetsType int type) { - mSources.remove(type); + mSources[type] = null; } /** @@ -344,53 +412,32 @@ public class InsetsState implements Parcelable { * @param visible {@code true} for visible */ public void setSourceVisible(@InternalInsetsType int type, boolean visible) { - InsetsSource source = mSources.get(type); + InsetsSource source = mSources[type]; if (source != null) { source.setVisible(visible); } } - /** - * A shortcut for setting the visibility of the source. - * - * @param type The {@link InternalInsetsType} of the source to set the visibility - * @param referenceState The {@link InsetsState} for reference - */ - public void setSourceVisible(@InternalInsetsType int type, InsetsState referenceState) { - InsetsSource source = mSources.get(type); - InsetsSource referenceSource = referenceState.mSources.get(type); - if (source != null && referenceSource != null) { - source.setVisible(referenceSource.isVisible()); - } - } - public void set(InsetsState other) { set(other, false /* copySources */); } public void set(InsetsState other, boolean copySources) { mDisplayFrame.set(other.mDisplayFrame); - mSources.clear(); if (copySources) { - for (int i = 0; i < other.mSources.size(); i++) { - InsetsSource source = other.mSources.valueAt(i); - mSources.put(source.getType(), new InsetsSource(source)); + for (int i = 0; i < SIZE; i++) { + InsetsSource source = other.mSources[i]; + mSources[i] = source != null ? new InsetsSource(source) : null; } } else { - mSources.putAll(other.mSources); + for (int i = 0; i < SIZE; i++) { + mSources[i] = other.mSources[i]; + } } } public void addSource(InsetsSource source) { - mSources.put(source.getType(), source); - } - - public int getSourcesCount() { - return mSources.size(); - } - - public InsetsSource sourceAt(int index) { - return mSources.valueAt(index); + mSources[source.getType()] = source; } public static @InternalInsetsType ArraySet toInternalType(@InsetsType int types) { @@ -452,7 +499,7 @@ public class InsetsState implements Parcelable { } } - public static boolean getDefaultVisibility(@InsetsType int type) { + public static boolean getDefaultVisibility(@InternalInsetsType int type) { return type != ITYPE_IME; } @@ -471,8 +518,10 @@ public class InsetsState implements Parcelable { public void dump(String prefix, PrintWriter pw) { pw.println(prefix + "InsetsState"); - for (int i = mSources.size() - 1; i >= 0; i--) { - mSources.valueAt(i).dump(prefix + " ", pw); + for (int i = 0; i < SIZE; i++) { + InsetsSource source = mSources[i]; + if (source == null) continue; + source.dump(prefix + " ", pw); } } @@ -517,7 +566,7 @@ public class InsetsState implements Parcelable { @Override public boolean equals(Object o) { - return equals(o, false); + return equals(o, false, false); } /** @@ -526,10 +575,13 @@ public class InsetsState implements Parcelable { * excluded. * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but * ignore the caption insets source value. + * @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is + * not visible. * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise. */ @VisibleForTesting - public boolean equals(Object o, boolean excludingCaptionInsets) { + public boolean equals(Object o, boolean excludingCaptionInsets, + boolean excludeInvisibleImeFrames) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } @@ -538,29 +590,19 @@ public class InsetsState implements Parcelable { if (!mDisplayFrame.equals(state.mDisplayFrame)) { return false; } - int size = mSources.size(); - int otherSize = state.mSources.size(); - if (excludingCaptionInsets) { - if (mSources.get(ITYPE_CAPTION_BAR) != null) { - size--; - } - if (state.mSources.get(ITYPE_CAPTION_BAR) != null) { - otherSize--; - } - } - if (size != otherSize) { - return false; - } - for (int i = mSources.size() - 1; i >= 0; i--) { - InsetsSource source = mSources.valueAt(i); + for (int i = 0; i < SIZE; i++) { if (excludingCaptionInsets) { - if (source.getType() == ITYPE_CAPTION_BAR) continue; + if (i == ITYPE_CAPTION_BAR) continue; + } + InsetsSource source = mSources[i]; + InsetsSource otherSource = state.mSources[i]; + if (source == null && otherSource == null) { + continue; } - InsetsSource otherSource = state.mSources.get(source.getType()); - if (otherSource == null) { + if (source != null && otherSource == null || source == null && otherSource != null) { return false; } - if (!otherSource.equals(source)) { + if (!otherSource.equals(source, excludeInvisibleImeFrames)) { return false; } } @@ -569,7 +611,7 @@ public class InsetsState implements Parcelable { @Override public int hashCode() { - return Objects.hash(mDisplayFrame, mSources); + return Objects.hash(mDisplayFrame, Arrays.hashCode(mSources)); } public InsetsState(Parcel in) { @@ -584,10 +626,7 @@ public class InsetsState implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(mDisplayFrame, flags); - dest.writeInt(mSources.size()); - for (int i = 0; i < mSources.size(); i++) { - dest.writeParcelable(mSources.valueAt(i), flags); - } + dest.writeParcelableArray(mSources, 0); } public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @@ -602,19 +641,15 @@ public class InsetsState implements Parcelable { }; public void readFromParcel(Parcel in) { - mSources.clear(); mDisplayFrame.set(in.readParcelable(null /* loader */)); - final int size = in.readInt(); - for (int i = 0; i < size; i++) { - final InsetsSource source = in.readParcelable(null /* loader */); - mSources.put(source.getType(), source); - } + mSources = in.readParcelableArray(null, InsetsSource.class); } @Override public String toString() { StringJoiner joiner = new StringJoiner(", "); - for (InsetsSource source : mSources.values()) { + for (int i = 0; i < SIZE; i++) { + InsetsSource source = mSources[i]; if (source != null) { joiner.add(source.toString()); } diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 19eff72ca814264c6777edc3915343a995e61a85..51b0c6b59f3c1dc32799579a99bbb828964484f2 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -486,6 +486,21 @@ public final class MotionEvent extends InputEvent implements Parcelable { */ public static final int FLAG_TAINTED = 0x80000000; + /** + * Private flag indicating that this event was synthesized by the system and should be delivered + * to the accessibility focused view first. When being dispatched such an event is not handled + * by predecessors of the accessibility focused view and after the event reaches that view the + * flag is cleared and normal event dispatch is performed. This ensures that the platform can + * click on any view that has accessibility focus which is semantically equivalent to asking the + * view to perform a click accessibility action but more generic as views not implementing click + * action correctly can still be activated. + * + * @hide + * @see #isTargetAccessibilityFocus() + * @see #setTargetAccessibilityFocus(boolean) + */ + public static final int FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000; + /** * Flag indicating the motion event intersected the top edge of the screen. */ @@ -2139,6 +2154,20 @@ public final class MotionEvent extends InputEvent implements Parcelable { nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED); } + /** @hide */ + public boolean isTargetAccessibilityFocus() { + final int flags = getFlags(); + return (flags & FLAG_TARGET_ACCESSIBILITY_FOCUS) != 0; + } + + /** @hide */ + public void setTargetAccessibilityFocus(boolean targetsFocus) { + final int flags = getFlags(); + nativeSetFlags(mNativePtr, targetsFocus + ? flags | FLAG_TARGET_ACCESSIBILITY_FOCUS + : flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS); + } + /** @hide */ public final boolean isHoverExitPending() { final int flags = getFlags(); diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java index 0283ada0dd40d8901087ca5a8ddc2179bef8d031..c018d1cf17820fea344c999f3771fb56424c833d 100644 --- a/core/java/android/view/PendingInsetsController.java +++ b/core/java/android/view/PendingInsetsController.java @@ -38,6 +38,7 @@ public class PendingInsetsController implements WindowInsetsController { private @Appearance int mAppearance; private @Appearance int mAppearanceMask; private @Behavior int mBehavior = KEEP_BEHAVIOR; + private boolean mAnimationsDisabled; private final InsetsState mDummyState = new InsetsState(); private InsetsController mReplayedInsetsController; private ArrayList mControllableInsetsChangedListeners @@ -102,6 +103,15 @@ public class PendingInsetsController implements WindowInsetsController { return mBehavior; } + @Override + public void setAnimationsDisabled(boolean disable) { + if (mReplayedInsetsController != null) { + mReplayedInsetsController.setAnimationsDisabled(disable); + } else { + mAnimationsDisabled = disable; + } + } + @Override public InsetsState getState() { return mDummyState; @@ -151,6 +161,9 @@ public class PendingInsetsController implements WindowInsetsController { if (mCaptionInsetsHeight != 0) { controller.setCaptionInsetsHeight(mCaptionInsetsHeight); } + if (mAnimationsDisabled) { + controller.setAnimationsDisabled(true); + } int size = mRequests.size(); for (int i = 0; i < size; i++) { mRequests.get(i).replay(controller); @@ -167,6 +180,7 @@ public class PendingInsetsController implements WindowInsetsController { mBehavior = KEEP_BEHAVIOR; mAppearance = 0; mAppearanceMask = 0; + mAnimationsDisabled = false; // After replaying, we forward everything directly to the replayed instance. mReplayedInsetsController = controller; diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 9109f50247e0aea567c23770d1a52c86e431e7fc..87b2f4b46df7ad09261cf5e0274fc1140dabaaf1 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -49,6 +49,7 @@ import android.os.Build; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.Trace; import android.util.ArrayMap; import android.util.Log; import android.util.SparseIntArray; @@ -62,8 +63,10 @@ import dalvik.system.CloseGuard; import libcore.util.NativeAllocationRegistry; import java.io.Closeable; +import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.ArrayList; import java.util.Objects; /** @@ -102,6 +105,8 @@ public final class SurfaceControl implements Parcelable { long otherTransactionObj); private static native void nativeSetAnimationTransaction(long transactionObj); private static native void nativeSetEarlyWakeup(long transactionObj); + private static native void nativeSetEarlyWakeupStart(long transactionObj); + private static native void nativeSetEarlyWakeupEnd(long transactionObj); private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder); private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject, @@ -223,25 +228,86 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetFixedTransformHint(long transactionObj, long nativeObject, int transformHint); + @Nullable + @GuardedBy("mLock") + private ArrayList mReparentListeners; + + /** + * Listener to observe surface reparenting. + * + * @hide + */ + public interface OnReparentListener { + + /** + * Callback for reparenting surfaces. + * + * Important: You should only interact with the provided surface control + * only if you have a contract with its owner to avoid them closing it + * under you or vise versa. + * + * @param transaction The transaction that would commit reparenting. + * @param parent The future parent surface. + */ + void onReparent(@NonNull Transaction transaction, @Nullable SurfaceControl parent); + } + private final CloseGuard mCloseGuard = CloseGuard.get(); private String mName; - /** + + /** * @hide */ public long mNativeObject; private long mNativeHandle; - private Throwable mReleaseStack = null; - // TODO: Move this to native. - private final Object mSizeLock = new Object(); - @GuardedBy("mSizeLock") + // TODO: Move width/height to native and fix locking through out. + private final Object mLock = new Object(); + @GuardedBy("mLock") private int mWidth; - @GuardedBy("mSizeLock") + @GuardedBy("mLock") private int mHeight; + private WeakReference mLocalOwnerView; + static Transaction sGlobalTransaction; static long sTransactionNestCount = 0; + /** + * Adds a reparenting listener. + * + * @param listener The listener. + * @return Whether listener was added. + * + * @hide + */ + public boolean addOnReparentListener(@NonNull OnReparentListener listener) { + synchronized (mLock) { + if (mReparentListeners == null) { + mReparentListeners = new ArrayList<>(1); + } + return mReparentListeners.add(listener); + } + } + + /** + * Removes a reparenting listener. + * + * @param listener The listener. + * @return Whether listener was removed. + * + * @hide + */ + public boolean removeOnReparentListener(@NonNull OnReparentListener listener) { + synchronized (mLock) { + final boolean removed = mReparentListeners.remove(listener); + if (mReparentListeners.isEmpty()) { + mReparentListeners = null; + } + return removed; + } + } + /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */ /** @@ -322,6 +388,14 @@ public final class SurfaceControl implements Parcelable { */ public static final int CURSOR_WINDOW = 0x00002000; + /** + * Surface creation flag: Indicates the effect layer will not have a color fill on + * creation. + * + * @hide + */ + public static final int NO_COLOR_FILL = 0x00004000; + /** * Surface creation flag: Creates a normal surface. * This is the default. @@ -425,32 +499,26 @@ public final class SurfaceControl implements Parcelable { private static final int INTERNAL_DATASPACE_DISPLAY_P3 = 143261696; private static final int INTERNAL_DATASPACE_SCRGB = 411107328; - private void assignNativeObject(long nativeObject) { + private void assignNativeObject(long nativeObject, String callsite) { if (mNativeObject != 0) { release(); } if (nativeObject != 0) { - mCloseGuard.open("release"); + mCloseGuard.openWithCallSite("release", callsite); } mNativeObject = nativeObject; mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0; - if (mNativeObject == 0) { - if (Build.IS_DEBUGGABLE) { - mReleaseStack = new Throwable("assigned zero nativeObject here"); - } - } else { - mReleaseStack = null; - } } /** * @hide */ - public void copyFrom(@NonNull SurfaceControl other) { + public void copyFrom(@NonNull SurfaceControl other, String callsite) { mName = other.mName; mWidth = other.mWidth; mHeight = other.mHeight; - assignNativeObject(nativeCopyFromSurfaceControl(other.mNativeObject)); + mLocalOwnerView = other.mLocalOwnerView; + assignNativeObject(nativeCopyFromSurfaceControl(other.mNativeObject), callsite); } /** @@ -548,8 +616,10 @@ public final class SurfaceControl implements Parcelable { private int mHeight; private int mFormat = PixelFormat.OPAQUE; private String mName; + private WeakReference mLocalOwnerView; private SurfaceControl mParent; private SparseIntArray mMetadata; + private String mCallsite = "SurfaceControl.Builder"; /** * Begin building a SurfaceControl with a given {@link SurfaceSession}. @@ -577,12 +647,13 @@ public final class SurfaceControl implements Parcelable { throw new IllegalStateException( "width and height must be positive or unset"); } - if ((mWidth > 0 || mHeight > 0) && (isColorLayerSet() || isContainerLayerSet())) { + if ((mWidth > 0 || mHeight > 0) && (isEffectLayer() || isContainerLayer())) { throw new IllegalStateException( "Only buffer layers can set a valid buffer size."); } return new SurfaceControl( - mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata); + mSession, mName, mWidth, mHeight, mFormat, mFlags, mParent, mMetadata, + mLocalOwnerView, mCallsite); } /** @@ -596,6 +667,27 @@ public final class SurfaceControl implements Parcelable { return this; } + /** + * Set the local owner view for the surface. This view is only + * valid in the same process and is not transferred in an IPC. + * + * Note: This is used for cases where we want to know the view + * that manages the surface control while intercepting reparenting. + * A specific example is InlineContentView which exposes is surface + * control for reparenting as a way to implement clipping of several + * InlineContentView instances within a certain area. + * + * @param view The owner view. + * @return This builder. + * + * @hide + */ + @NonNull + public Builder setLocalOwnerView(@NonNull View view) { + mLocalOwnerView = new WeakReference<>(view); + return this; + } + /** * Set the initial size of the controlled surface's buffers in pixels. * @@ -749,10 +841,27 @@ public final class SurfaceControl implements Parcelable { } /** - * Indicate whether a 'ColorLayer' is to be constructed. + * Indicate whether an 'EffectLayer' is to be constructed. * - * Color layers will not have an associated BufferQueue and will instead always render a - * solid color (that is, solid before plane alpha). Currently that color is black. + * An effect layer behaves like a container layer by default but it can support + * color fill, shadows and/or blur. These layers will not have an associated buffer. + * When created, this layer has no effects set and will be transparent but the caller + * can render an effect by calling: + * - {@link Transaction#setColor(SurfaceControl, float[])} + * - {@link Transaction#setBackgroundBlurRadius(SurfaceControl, int)} + * - {@link Transaction#setShadowRadius(SurfaceControl, float)} + * + * @hide + */ + public Builder setEffectLayer() { + mFlags |= NO_COLOR_FILL; + unsetBufferSize(); + return setFlags(FX_SURFACE_EFFECT, FX_SURFACE_MASK); + } + + /** + * A convenience function to create an effect layer with a default color fill + * applied to it. Currently that color is black. * * @hide */ @@ -761,7 +870,7 @@ public final class SurfaceControl implements Parcelable { return setFlags(FX_SURFACE_EFFECT, FX_SURFACE_MASK); } - private boolean isColorLayerSet() { + private boolean isEffectLayer() { return (mFlags & FX_SURFACE_EFFECT) == FX_SURFACE_EFFECT; } @@ -786,7 +895,7 @@ public final class SurfaceControl implements Parcelable { return setFlags(FX_SURFACE_CONTAINER, FX_SURFACE_MASK); } - private boolean isContainerLayerSet() { + private boolean isContainerLayer() { return (mFlags & FX_SURFACE_CONTAINER) == FX_SURFACE_CONTAINER; } @@ -802,6 +911,18 @@ public final class SurfaceControl implements Parcelable { return this; } + /** + * Sets the callsite this SurfaceControl is constructed from. + * + * @param callsite String uniquely identifying callsite that created this object. Used for + * leakage tracking. + * @hide + */ + public Builder setCallsite(String callsite) { + mCallsite = callsite; + return this; + } + private Builder setFlags(int flags, int mask) { mFlags = (mFlags & ~mask) | flags; return this; @@ -833,10 +954,13 @@ public final class SurfaceControl implements Parcelable { * @param h The surface initial height. * @param flags The surface creation flags. * @param metadata Initial metadata. + * @param callsite String uniquely identifying callsite that created this object. Used for + * leakage tracking. * @throws throws OutOfResourcesException If the SurfaceControl cannot be created. */ private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags, - SurfaceControl parent, SparseIntArray metadata) + SurfaceControl parent, SparseIntArray metadata, WeakReference localOwnerView, + String callsite) throws OutOfResourcesException, IllegalArgumentException { if (name == null) { throw new IllegalArgumentException("name must not be null"); @@ -845,6 +969,7 @@ public final class SurfaceControl implements Parcelable { mName = name; mWidth = w; mHeight = h; + mLocalOwnerView = localOwnerView; Parcel metaParcel = Parcel.obtain(); try { if (metadata != null && metadata.size() > 0) { @@ -867,18 +992,20 @@ public final class SurfaceControl implements Parcelable { "Couldn't allocate SurfaceControl native object"); } mNativeHandle = nativeGetHandle(mNativeObject); - mCloseGuard.open("release"); + mCloseGuard.openWithCallSite("release", callsite); } /** * Copy constructor. Creates a new native object pointing to the same surface as {@code other}. * * @param other The object to copy the surface from. + * @param callsite String uniquely identifying callsite that created this object. Used for + * leakage tracking. * @hide */ @TestApi - public SurfaceControl(@NonNull SurfaceControl other) { - copyFrom(other); + public SurfaceControl(@NonNull SurfaceControl other, @NonNull String callsite) { + copyFrom(other, callsite); } private SurfaceControl(Parcel in) { @@ -904,7 +1031,7 @@ public final class SurfaceControl implements Parcelable { if (in.readInt() != 0) { object = nativeReadFromParcel(in); } - assignNativeObject(object); + assignNativeObject(object, "readFromParcel"); } @Override @@ -999,21 +1126,10 @@ public final class SurfaceControl implements Parcelable { nativeRelease(mNativeObject); mNativeObject = 0; mNativeHandle = 0; - if (Build.IS_DEBUGGABLE) { - mReleaseStack = new Throwable("released here"); - } mCloseGuard.close(); } } - /** - * Returns the call stack that assigned mNativeObject to zero. - * @hide - */ - public Throwable getReleaseStack() { - return mReleaseStack; - } - /** * Disconnect any client still connected to the surface. * @hide @@ -1025,11 +1141,8 @@ public final class SurfaceControl implements Parcelable { } private void checkNotReleased() { - if (mNativeObject == 0) { - Log.wtf(TAG, "Invalid " + this + " caused by:", mReleaseStack); - throw new NullPointerException( - "mNativeObject of " + this + " is null. Have you called release() already?"); - } + if (mNativeObject == 0) throw new NullPointerException( + "Invalid " + this + ", mNativeObject is null. Have you called release() already?"); } /** @@ -1299,7 +1412,7 @@ public final class SurfaceControl implements Parcelable { * @hide */ public int getWidth() { - synchronized (mSizeLock) { + synchronized (mLock) { return mWidth; } } @@ -1308,11 +1421,22 @@ public final class SurfaceControl implements Parcelable { * @hide */ public int getHeight() { - synchronized (mSizeLock) { + synchronized (mLock) { return mHeight; } } + /** + * Gets the local view that owns this surface. + * + * @return The owner view. + * + * @hide + */ + public @Nullable View getLocalOwnerView() { + return (mLocalOwnerView != null) ? mLocalOwnerView.get() : null; + } + @Override public String toString() { return "Surface(name=" + mName + ")/@0x" + @@ -2101,7 +2225,7 @@ public final class SurfaceControl implements Parcelable { public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf) { long nativeObj = nativeMirrorSurface(mirrorOf.mNativeObject); SurfaceControl sc = new SurfaceControl(); - sc.assignNativeObject(nativeObj); + sc.assignNativeObject(nativeObj, "mirrorSurface"); return sc; } @@ -2157,6 +2281,9 @@ public final class SurfaceControl implements Parcelable { public long mNativeObject; private final ArrayMap mResizedSurfaces = new ArrayMap<>(); + private final ArrayMap mReparentedSurfaces = + new ArrayMap<>(); + Runnable mFreeNativeResources; private static final float[] INVALID_COLOR = {-1, -1, -1}; @@ -2197,6 +2324,8 @@ public final class SurfaceControl implements Parcelable { */ @Override public void close() { + mResizedSurfaces.clear(); + mReparentedSurfaces.clear(); mFreeNativeResources.run(); mNativeObject = 0; } @@ -2207,6 +2336,7 @@ public final class SurfaceControl implements Parcelable { */ public void apply(boolean sync) { applyResizedSurfaces(); + notifyReparentedSurfaces(); nativeApplyTransaction(mNativeObject, sync); } @@ -2214,7 +2344,7 @@ public final class SurfaceControl implements Parcelable { for (int i = mResizedSurfaces.size() - 1; i >= 0; i--) { final Point size = mResizedSurfaces.valueAt(i); final SurfaceControl surfaceControl = mResizedSurfaces.keyAt(i); - synchronized (surfaceControl.mSizeLock) { + synchronized (surfaceControl.mLock) { surfaceControl.mWidth = size.x; surfaceControl.mHeight = size.y; } @@ -2222,6 +2352,22 @@ public final class SurfaceControl implements Parcelable { mResizedSurfaces.clear(); } + private void notifyReparentedSurfaces() { + final int reparentCount = mReparentedSurfaces.size(); + for (int i = reparentCount - 1; i >= 0; i--) { + final SurfaceControl child = mReparentedSurfaces.keyAt(i); + synchronized (child.mLock) { + final int listenerCount = (child.mReparentListeners != null) + ? child.mReparentListeners.size() : 0; + for (int j = 0; j < listenerCount; j++) { + final OnReparentListener listener = child.mReparentListeners.get(j); + listener.onReparent(this, mReparentedSurfaces.valueAt(i)); + } + mReparentedSurfaces.removeAt(i); + } + } + } + /** * Toggle the visibility of a given Layer and it's sub-tree. * @@ -2624,6 +2770,7 @@ public final class SurfaceControl implements Parcelable { otherObject = newParent.mNativeObject; } nativeReparent(mNativeObject, sc.mNativeObject, otherObject); + mReparentedSurfaces.put(sc, newParent); return this; } @@ -2772,6 +2919,8 @@ public final class SurfaceControl implements Parcelable { } /** + * @deprecated use {@link Transaction#setEarlyWakeupStart()} + * * Indicate that SurfaceFlinger should wake up earlier than usual as a result of this * transaction. This should be used when the caller thinks that the scene is complex enough * that it's likely to hit GL composition, and thus, SurfaceFlinger needs to more time in @@ -2780,11 +2929,35 @@ public final class SurfaceControl implements Parcelable { * Corresponds to setting ISurfaceComposer::eEarlyWakeup * @hide */ + @Deprecated public Transaction setEarlyWakeup() { nativeSetEarlyWakeup(mNativeObject); return this; } + /** + * Provides a hint to SurfaceFlinger to change its offset so that SurfaceFlinger wakes up + * earlier to compose surfaces. The caller should use this as a hint to SurfaceFlinger + * when the scene is complex enough to use GPU composition. The hint will remain active + * until until the client calls {@link Transaction#setEarlyWakeupEnd}. + * + * @hide + */ + public Transaction setEarlyWakeupStart() { + nativeSetEarlyWakeupStart(mNativeObject); + return this; + } + + /** + * Removes the early wake up hint set by {@link Transaction#setEarlyWakeupStart}. + * + * @hide + */ + public Transaction setEarlyWakeupEnd() { + nativeSetEarlyWakeupEnd(mNativeObject); + return this; + } + /** * Sets an arbitrary piece of metadata on the surface. This is a helper for int data. * @hide @@ -2878,6 +3051,8 @@ public final class SurfaceControl implements Parcelable { } mResizedSurfaces.putAll(other.mResizedSurfaces); other.mResizedSurfaces.clear(); + mReparentedSurfaces.putAll(other.mReparentedSurfaces); + other.mReparentedSurfaces.clear(); nativeMergeTransaction(mNativeObject, other.mNativeObject); return this; } diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 385078165e844a6c4f522da8b79eaebf130c6602..66ab3a32edfa644f3e188dff8e0f7f6a7faf91b7 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -167,9 +167,10 @@ public class SurfaceControlViewHost { public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display, @Nullable IBinder hostToken) { mSurfaceControl = new SurfaceControl.Builder() - .setContainerLayer() - .setName("SurfaceControlViewHost") - .build(); + .setContainerLayer() + .setName("SurfaceControlViewHost") + .setCallsite("SurfaceControlViewHost") + .build(); mWm = new WindowlessWindowManager(context.getResources().getConfiguration(), mSurfaceControl, hostToken); mViewRoot = new ViewRootImpl(context, display, mWm); @@ -177,6 +178,17 @@ public class SurfaceControlViewHost { mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } + /** + * @hide + */ + @Override + protected void finalize() throws Throwable { + // We aren't on the UI thread here so we need to pass false to + // doDie + mViewRoot.die(false /* immediate */); + } + + /** * Return a SurfacePackage for the root SurfaceControl of the embedded hierarchy. * Rather than be directly reparented using {@link SurfaceControl.Transaction} this @@ -273,6 +285,6 @@ public class SurfaceControlViewHost { */ public void release() { // ViewRoot will release mSurfaceControl for us. - mViewRoot.die(false /* immediate */); + mViewRoot.die(true /* immediate */); } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index bd811fc1f052823d0924f29b2cfa698733216ec0..f937bc9e84a9d5c1489d387c27d6c3041c680871 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -44,7 +44,6 @@ import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceControl.Transaction; -import android.view.SurfaceControlViewHost; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.IAccessibilityEmbeddedConnection; @@ -134,6 +133,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // we need to preserve the old one until the new one has drawn. SurfaceControl mDeferredDestroySurfaceControl; SurfaceControl mBackgroundControl; + private boolean mDisableBackgroundLayer = false; /** * We use this lock in SOME cases when reading or writing SurfaceControl, @@ -245,10 +245,17 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + this(context, attrs, defStyleAttr, defStyleRes, false); + } + + /** @hide */ + public SurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes, boolean disableBackgroundLayer) { super(context, attrs, defStyleAttr, defStyleRes); mRenderNode.addPositionUpdateListener(mPositionListener); setWillNotDraw(true); + mDisableBackgroundLayer = disableBackgroundLayer; } /** @@ -464,6 +471,13 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private void performDrawFinished() { + if (mDeferredDestroySurfaceControl != null) { + synchronized (mSurfaceControlLock) { + mTmpTransaction.remove(mDeferredDestroySurfaceControl).apply(); + mDeferredDestroySurfaceControl = null; + } + } + if (mPendingReportDraws > 0) { mDrawFinished = true; if (mAttachedToWindow) { @@ -632,7 +646,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mTmpRect.set(0, 0, mSurfaceWidth, mSurfaceHeight); } SyncRtSurfaceTransactionApplier applier = new SyncRtSurfaceTransactionApplier(this); - applier.scheduleApply(false /* earlyWakeup */, + applier.scheduleApply( new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(mSurfaceControl) .withWindowCrop(mTmpRect) .build()); @@ -839,7 +853,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall if (mBackgroundControl == null) { return; } - if ((mSubLayer < 0) && ((mSurfaceFlags & SurfaceControl.OPAQUE) != 0)) { + if ((mSubLayer < 0) && ((mSurfaceFlags & SurfaceControl.OPAQUE) != 0) + && !mDisableBackgroundLayer) { t.show(mBackgroundControl); } else { t.hide(mBackgroundControl); @@ -884,12 +899,15 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } return; } - ViewRootImpl viewRoot = getViewRootImpl(); - if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) { - if (DEBUG) { - Log.d(TAG, System.identityHashCode(this) - + " updateSurface: no valid surface"); - } + final ViewRootImpl viewRoot = getViewRootImpl(); + + if (viewRoot == null) { + return; + } + + if (viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) { + notifySurfaceDestroyed(); + releaseSurfaces(); return; } @@ -976,17 +994,21 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession) .setName(name) + .setLocalOwnerView(this) .setOpaque((mSurfaceFlags & SurfaceControl.OPAQUE) != 0) .setBufferSize(mSurfaceWidth, mSurfaceHeight) .setFormat(mFormat) .setParent(viewRoot.getBoundsLayer()) .setFlags(mSurfaceFlags) + .setCallsite("SurfaceView.updateSurface") .build(); mBackgroundControl = new SurfaceControl.Builder(mSurfaceSession) .setName("Background for -" + name) + .setLocalOwnerView(this) .setOpaque(true) .setColorLayer() .setParent(mSurfaceControl) + .setCallsite("SurfaceView.updateSurface") .build(); } else if (mSurfaceControl == null) { @@ -1039,11 +1061,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // we still need to latch a buffer). // b/28866173 if (sizeChanged || creating || !mRtHandlingPositionUpdates) { - mTmpTransaction.setPosition(mSurfaceControl, mScreenRect.left, - mScreenRect.top); - mTmpTransaction.setMatrix(mSurfaceControl, - mScreenRect.width() / (float) mSurfaceWidth, 0.0f, 0.0f, - mScreenRect.height() / (float) mSurfaceHeight); + onSetSurfacePositionAndScaleRT(mTmpTransaction, mSurfaceControl, + mScreenRect.left, /*positionLeft*/ + mScreenRect.top /*positionTop*/ , + mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/, + mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/); + // Set a window crop when creating the surface or changing its size to // crop the buffer to the surface size since the buffer producer may // use SCALING_MODE_SCALE and submit a larger size than the surface @@ -1100,28 +1123,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall final boolean surfaceChanged = creating; if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) { mSurfaceCreated = false; - if (mSurface.isValid()) { - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "visibleChanged -- surfaceDestroyed"); - callbacks = getSurfaceCallbacks(); - for (SurfaceHolder.Callback c : callbacks) { - c.surfaceDestroyed(mSurfaceHolder); - } - // Since Android N the same surface may be reused and given to us - // again by the system server at a later point. However - // as we didn't do this in previous releases, clients weren't - // necessarily required to clean up properly in - // surfaceDestroyed. This leads to problems for example when - // clients don't destroy their EGL context, and try - // and create a new one on the same surface following reuse. - // Since there is no valid use of the surface in-between - // surfaceDestroyed and surfaceCreated, we force a disconnect, - // so the next connect will always work if we end up reusing - // the surface. - if (mSurface.isValid()) { - mSurface.forceScopedDisconnect(); - } - } + notifySurfaceDestroyed(); } if (creating) { @@ -1199,11 +1201,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall + "finishedDrawing"); } - if (mDeferredDestroySurfaceControl != null) { - mTmpTransaction.remove(mDeferredDestroySurfaceControl).apply(); - mDeferredDestroySurfaceControl = null; - } - runOnUiThread(this::performDrawFinished); } @@ -1218,6 +1215,40 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall Surface viewRootSurface, long nextViewRootFrameNumber) { } + /** + * Sets the surface position and scale. Can be called on + * the UI thread as well as on the renderer thread. + * + * @param transaction Transaction in which to execute. + * @param surface Surface whose location to set. + * @param positionLeft The left position to set. + * @param positionTop The top position to set. + * @param postScaleX The X axis post scale + * @param postScaleY The Y axis post scale + * + * @hide + */ + protected void onSetSurfacePositionAndScaleRT(@NonNull Transaction transaction, + @NonNull SurfaceControl surface, int positionLeft, int positionTop, + float postScaleX, float postScaleY) { + transaction.setPosition(surface, positionLeft, positionTop); + transaction.setMatrix(surface, postScaleX /*dsdx*/, 0f /*dtdx*/, + 0f /*dtdy*/, postScaleY /*dsdy*/); + } + + /** @hide */ + public void requestUpdateSurfacePositionAndScale() { + if (mSurfaceControl == null) { + return; + } + onSetSurfacePositionAndScaleRT(mTmpTransaction, mSurfaceControl, + mScreenRect.left, /*positionLeft*/ + mScreenRect.top/*positionTop*/ , + mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/, + mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/); + mTmpTransaction.apply(); + } + private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t, Rect position, long frameNumber) { final ViewRootImpl viewRoot = getViewRootImpl(); @@ -1226,16 +1257,26 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall frameNumber); } - t.setPosition(surface, position.left, position.top); - t.setMatrix(surface, - position.width() / (float) mSurfaceWidth, - 0.0f, 0.0f, - position.height() / (float) mSurfaceHeight); + onSetSurfacePositionAndScaleRT(t, surface, + position.left /*positionLeft*/, + position.top /*positionTop*/, + position.width() / (float) mSurfaceWidth /*postScaleX*/, + position.height() / (float) mSurfaceHeight /*postScaleY*/); + if (mViewVisibility) { t.show(surface); } } + /** + * @return The last render position of the backing surface or an empty rect. + * + * @hide + */ + public @NonNull Rect getSurfaceRenderPosition() { + return mRTLastReportedPosition; + } + private void setParentSpaceRectangle(Rect position, long frameNumber) { final ViewRootImpl viewRoot = getViewRootImpl(); final boolean useBLAST = viewRoot.isDrawingToBLASTTransaction(); @@ -1627,9 +1668,14 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private void updateRelativeZ(Transaction t) { - SurfaceControl viewRoot = getViewRootImpl().getSurfaceControl(); - t.setRelativeLayer(mBackgroundControl, viewRoot, Integer.MIN_VALUE); - t.setRelativeLayer(mSurfaceControl, viewRoot, mSubLayer); + final ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot == null) { + // We were just detached. + return; + } + final SurfaceControl viewRootControl = viewRoot.getSurfaceControl(); + t.setRelativeLayer(mBackgroundControl, viewRootControl, Integer.MIN_VALUE); + t.setRelativeLayer(mSurfaceControl, viewRootControl, mSubLayer); } /** @@ -1775,6 +1821,31 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } } + private void notifySurfaceDestroyed() { + if (mSurface.isValid()) { + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "surfaceDestroyed"); + SurfaceHolder.Callback[] callbacks = getSurfaceCallbacks(); + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceDestroyed(mSurfaceHolder); + } + // Since Android N the same surface may be reused and given to us + // again by the system server at a later point. However + // as we didn't do this in previous releases, clients weren't + // necessarily required to clean up properly in + // surfaceDestroyed. This leads to problems for example when + // clients don't destroy their EGL context, and try + // and create a new one on the same surface following reuse. + // Since there is no valid use of the surface in-between + // surfaceDestroyed and surfaceCreated, we force a disconnect, + // so the next connect will always work if we end up reusing + // the surface. + if (mSurface.isValid()) { + mSurface.forceScopedDisconnect(); + } + } + } + /** * Wrapper of accessibility embedded connection for embedded view hierarchy. */ diff --git a/core/java/android/view/SyncRtSurfaceTransactionApplier.java b/core/java/android/view/SyncRtSurfaceTransactionApplier.java index 9c97f3e5b503c45a9bd9cf9704a1e41d4a89536f..062285ff2f5dc57cbd223efe031d12b96ea43add 100644 --- a/core/java/android/view/SyncRtSurfaceTransactionApplier.java +++ b/core/java/android/view/SyncRtSurfaceTransactionApplier.java @@ -53,11 +53,10 @@ public class SyncRtSurfaceTransactionApplier { /** * Schedules applying surface parameters on the next frame. * - * @param earlyWakeup Whether to set {@link Transaction#setEarlyWakeup()} on transaction. * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into * this method to avoid synchronization issues. */ - public void scheduleApply(boolean earlyWakeup, final SurfaceParams... params) { + public void scheduleApply(final SurfaceParams... params) { if (mTargetViewRootImpl == null) { return; } @@ -67,7 +66,7 @@ public class SyncRtSurfaceTransactionApplier { return; } Transaction t = new Transaction(); - applyParams(t, frame, earlyWakeup, params); + applyParams(t, frame, params); }); // Make sure a frame gets scheduled. @@ -78,12 +77,10 @@ public class SyncRtSurfaceTransactionApplier { * Applies surface parameters on the next frame. * @param t transaction to apply all parameters in. * @param frame frame to synchronize to. Set -1 when sync is not required. - * @param earlyWakeup Whether to set {@link Transaction#setEarlyWakeup()} on transaction. * @param params The surface parameters to apply. DO NOT MODIFY the list after passing into * this method to avoid synchronization issues. */ - void applyParams(Transaction t, long frame, boolean earlyWakeup, - final SurfaceParams... params) { + void applyParams(Transaction t, long frame, final SurfaceParams... params) { for (int i = params.length - 1; i >= 0; i--) { SurfaceParams surfaceParams = params[i]; SurfaceControl surface = surfaceParams.surface; @@ -92,9 +89,6 @@ public class SyncRtSurfaceTransactionApplier { } applyParams(t, surfaceParams, mTmpFloat9); } - if (earlyWakeup) { - t.setEarlyWakeup(); - } t.apply(); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1226202dfdf9a17181e6ee86d86821e8e4a9f15b..ca424e79ed7b7c74f2c8fac13a174dd6b89edb33 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -14274,6 +14274,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. + if (event.isTargetAccessibilityFocus()) { + // We don't have focus or no virtual descendant has it, do not handle the event. + if (!isAccessibilityFocusedViewOrHost()) { + return false; + } + // We have focus and got the event, then use normal event dispatch. + event.setTargetAccessibilityFocus(false); + } boolean result = false; if (mInputEventConsistencyVerifier != null) { @@ -26364,6 +26372,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, .setParent(root.getSurfaceControl()) .setBufferSize(shadowSize.x, shadowSize.y) .setFormat(PixelFormat.TRANSLUCENT) + .setCallsite("View.startDragAndDrop") .build(); final Surface surface = new Surface(); surface.copyFrom(surfaceControl); diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 0d2d4d13eb387a10dc2bd60e7425f08a58243393..ffeeb806ba54fa5a7204ce30cfcc6f2f1b478eaf 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -500,12 +500,13 @@ public class ViewConfiguration { */ public static ViewConfiguration get(Context context) { if (!context.isUiContext() && vmIncorrectContextUseEnabled()) { - final String errorMessage = "Tried to access UI constants from a non-visual Context."; + final String errorMessage = "Tried to access UI constants from a non-visual Context:" + + context; final String message = "UI constants, such as display metrics or window metrics, " + "must be accessed from Activity or other visual Context. " + "Use an Activity or a Context created with " + "Context#createWindowContext(int, Bundle), which are adjusted to the " - + "configuration and visual bounds of an area on screen."; + + "configuration and visual bounds of an area on screen"; final Exception exception = new IllegalArgumentException(errorMessage); StrictMode.onIncorrectContextUsed(message, exception); Log.e(TAG, errorMessage + message, exception); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index e3362aafbcd465ea3551b69f39d95d3dab3fbe53..77fedd7c30d4ac173994774cdb15de22140e66f8 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2048,8 +2048,26 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); + View childWithAccessibilityFocus = + event.isTargetAccessibilityFocus() + ? findChildWithAccessibilityFocus() + : null; + if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { + + // If there is a view that has accessibility focus we want it + // to get the event first and if not handled we will perform a + // normal dispatch. We may do a double iteration but this is + // safer given the timeframe. + if (childWithAccessibilityFocus != null) { + if (childWithAccessibilityFocus != child) { + continue; + } + childWithAccessibilityFocus = null; + i = childrenCount - 1; + } + event.setTargetAccessibilityFocus(false); continue; } final PointerIcon pointerIcon = @@ -2617,6 +2635,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } + // If the event targets the accessibility focused view and this is it, start + // normal event dispatch. Maybe a descendant is what will handle the click. + if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { + ev.setTargetAccessibilityFocus(false); + } + boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); @@ -2647,6 +2671,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // so this view group continues to intercept touches. intercepted = true; } + + // If intercepted, start normal event dispatch. Also if there is already + // a view that is handling the gesture, do normal event dispatch. + if (intercepted || mFirstTouchTarget != null) { + ev.setTargetAccessibilityFocus(false); + } + // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; @@ -2658,6 +2689,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { + // If the event is targeting accessibility focus we give it to the + // view that has accessibility focus and if it does not handle it + // we clear the flag and dispatch the event to all children as usual. + // We are looking up the accessibility focused host to avoid keeping + // state since these events are very rare. + View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() + ? findChildWithAccessibilityFocus() : null; + if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { @@ -2720,6 +2759,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager alreadyDispatchedToNewTouchTarget = true; break; } + + // The accessibility focus didn't handle the event, so clear + // the flag and do a normal dispatch to all children. + ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } @@ -2803,6 +2846,34 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return buildOrderedChildList(); } + /** + * Finds the child which has accessibility focus. + * + * @return The child that has focus. + */ + private View findChildWithAccessibilityFocus() { + ViewRootImpl viewRoot = getViewRootImpl(); + if (viewRoot == null) { + return null; + } + + View current = viewRoot.getAccessibilityFocusedHost(); + if (current == null) { + return null; + } + + ViewParent parent = current.getParent(); + while (parent instanceof View) { + if (parent == this) { + return current; + } + current = (View) parent; + parent = current.getParent(); + } + + return null; + } + /** * Resets all touch state in preparation for a new cycle. */ @@ -3257,9 +3328,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager break; } default: - throw new IllegalStateException("descendant focusability must be " - + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS " - + "but is " + descendantFocusability); + throw new IllegalStateException( + "descendant focusability must be one of FOCUS_BEFORE_DESCENDANTS," + + " FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS but is " + + descendantFocusability); } if (result && !isLayoutValid() && ((mPrivateFlags & PFLAG_WANTS_FOCUS) == 0)) { mPrivateFlags |= PFLAG_WANTS_FOCUS; @@ -4925,7 +4997,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (params == null) { params = generateDefaultLayoutParams(); if (params == null) { - throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null"); + throw new IllegalArgumentException( + "generateDefaultLayoutParams() cannot return null"); } } addView(child, index, params); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8b5d033072fbbd3d222836605846b4528c6ce47e..fefe564787ca41b78899e517a42dde7dcbc26c33 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -21,7 +21,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; -import static android.view.InsetsState.LAST_TYPE; +import static android.view.InsetsState.SIZE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; @@ -564,7 +564,7 @@ public final class ViewRootImpl implements ViewParent, new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); boolean mPendingAlwaysConsumeSystemBars; private final InsetsState mTempInsets = new InsetsState(); - private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[LAST_TYPE + 1]; + private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE]; final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets = new ViewTreeObserver.InternalInsetsInfo(); @@ -1726,8 +1726,10 @@ public final class ViewRootImpl implements ViewParent, destroySurface(); } } + scheduleConsumeBatchedInputImmediately(); } + /** Register callbacks to be notified when the ViewRootImpl surface changes. */ interface SurfaceChangedCallback { void surfaceCreated(Transaction t); @@ -1780,6 +1782,7 @@ public final class ViewRootImpl implements ViewParent, .setContainerLayer() .setName("Bounds for - " + getTitle().toString()) .setParent(getRenderSurfaceControl()) + .setCallsite("ViewRootImpl.getBoundsLayer") .build(); setBoundsLayerCrop(); mTransaction.show(mBoundsLayer).apply(); @@ -1821,13 +1824,19 @@ public final class ViewRootImpl implements ViewParent, /** * Called after window layout to update the bounds surface. If the surface insets have changed * or the surface has resized, update the bounds surface. + * + * @param shouldReparent Whether it should reparent the bounds layer to the main SurfaceControl. */ - private void updateBoundsLayer() { + private void updateBoundsLayer(boolean shouldReparent) { if (mBoundsLayer != null) { setBoundsLayerCrop(); - mTransaction.deferTransactionUntil(mBoundsLayer, - getRenderSurfaceControl(), mSurface.getNextFrameNumber()) - .apply(); + mTransaction.deferTransactionUntil(mBoundsLayer, getRenderSurfaceControl(), + mSurface.getNextFrameNumber()); + + if (shouldReparent) { + mTransaction.reparent(mBoundsLayer, getRenderSurfaceControl()); + } + mTransaction.apply(); } } @@ -2314,7 +2323,7 @@ public final class ViewRootImpl implements ViewParent, || lp.type == TYPE_VOLUME_OVERLAY; } - private int dipToPx(int dip) { + int dipToPx(int dip) { final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); return (int) (displayMetrics.density * dip + 0.5f); } @@ -2722,7 +2731,6 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mThreadedRenderer.isEnabled()) { mAttachInfo.mThreadedRenderer.destroy(); } - notifySurfaceDestroyed(); } else if ((surfaceReplaced || surfaceSizeChanged || windowRelayoutWasForced || colorModeChanged) && mSurfaceHolder == null @@ -2910,7 +2918,16 @@ public final class ViewRootImpl implements ViewParent, } if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) { - updateBoundsLayer(); + // If the surface has been replaced, there's a chance the bounds layer is not parented + // to the new layer. When updating bounds layer, also reparent to the main VRI + // SurfaceControl to ensure it's correctly placed in the hierarchy. + // + // This needs to be done on the client side since WMS won't reparent the children to the + // new surface if it thinks the app is closing. WMS gets the signal that the app is + // stopping, but on the client side it doesn't get stopped since it's restarted quick + // enough. WMS doesn't want to keep around old children since they will leak when the + // client creates new children. + updateBoundsLayer(surfaceReplaced); } final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); @@ -2953,6 +2970,10 @@ public final class ViewRootImpl implements ViewParent, } } + if (surfaceDestroyed) { + notifySurfaceDestroyed(); + } + if (triggerGlobalLayoutListener) { mAttachInfo.mRecomputeGlobalAttributes = false; mAttachInfo.mTreeObserver.dispatchOnGlobalLayout(); @@ -4616,6 +4637,9 @@ public final class ViewRootImpl implements ViewParent, } void dispatchDetachedFromWindow() { + // Make sure we free-up insets resources if view never received onWindowFocusLost() + // because of a die-signal + mInsetsController.onWindowFocusLost(); mFirstInputStage.onDetachedFromWindow(); if (mView != null && mView.mAttachInfo != null) { mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false); @@ -8104,7 +8128,9 @@ public final class ViewRootImpl implements ViewParent, } void scheduleConsumeBatchedInput() { - if (!mConsumeBatchedInputScheduled) { + // If anything is currently scheduled to consume batched input then there's no point in + // scheduling it again. + if (!mConsumeBatchedInputScheduled && !mConsumeBatchedInputImmediatelyScheduled) { mConsumeBatchedInputScheduled = true; mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, mConsumedBatchedInputRunnable, null); @@ -8127,22 +8153,15 @@ public final class ViewRootImpl implements ViewParent, } } - void doConsumeBatchedInput(long frameTimeNanos) { - if (mConsumeBatchedInputScheduled) { - mConsumeBatchedInputScheduled = false; - if (mInputEventReceiver != null) { - if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos) - && frameTimeNanos != -1) { - // If we consumed a batch here, we want to go ahead and schedule the - // consumption of batched input events on the next frame. Otherwise, we would - // wait until we have more input events pending and might get starved by other - // things occurring in the process. If the frame time is -1, however, then - // we're in a non-batching mode, so there's no need to schedule this. - scheduleConsumeBatchedInput(); - } - } - doProcessInputEvents(); + boolean doConsumeBatchedInput(long frameTimeNanos) { + final boolean consumedBatches; + if (mInputEventReceiver != null) { + consumedBatches = mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos); + } else { + consumedBatches = false; } + doProcessInputEvents(); + return consumedBatches; } final class TraversalRunnable implements Runnable { @@ -8186,8 +8205,11 @@ public final class ViewRootImpl implements ViewParent, @Override public void onBatchedInputEventPending(int source) { + // mStopped: There will be no more choreographer callbacks if we are stopped, + // so we must consume all input immediately to prevent ANR final boolean unbuffered = mUnbufferedInputDispatch - || (source & mUnbufferedInputSource) != SOURCE_CLASS_NONE; + || (source & mUnbufferedInputSource) != SOURCE_CLASS_NONE + || mStopped; if (unbuffered) { if (mConsumeBatchedInputScheduled) { unscheduleConsumeBatchedInput(); @@ -8215,7 +8237,14 @@ public final class ViewRootImpl implements ViewParent, final class ConsumeBatchedInputRunnable implements Runnable { @Override public void run() { - doConsumeBatchedInput(mChoreographer.getFrameTimeNanos()); + mConsumeBatchedInputScheduled = false; + if (doConsumeBatchedInput(mChoreographer.getFrameTimeNanos())) { + // If we consumed a batch here, we want to go ahead and schedule the + // consumption of batched input events on the next frame. Otherwise, we would + // wait until we have more input events pending and might get starved by other + // things occurring in the process. + scheduleConsumeBatchedInput(); + } } } final ConsumeBatchedInputRunnable mConsumedBatchedInputRunnable = @@ -8225,6 +8254,7 @@ public final class ViewRootImpl implements ViewParent, final class ConsumeBatchedInputImmediatelyRunnable implements Runnable { @Override public void run() { + mConsumeBatchedInputImmediatelyScheduled = false; doConsumeBatchedInput(-1); } } @@ -9365,6 +9395,11 @@ public final class ViewRootImpl implements ViewParent, return mInputEventReceiver.getToken(); } + @NonNull + public IBinder getWindowToken() { + return mAttachInfo.mWindowToken; + } + /** * Class for managing the accessibility interaction connection * based on the global accessibility state. diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java index 686d561a1a5a51bccb27f3a90bb124262eb61ad9..90a80cefc54d770de4458c54194ab019626e38fe 100644 --- a/core/java/android/view/ViewRootInsetsControllerHost.java +++ b/core/java/android/view/ViewRootInsetsControllerHost.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONT import android.annotation.NonNull; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.inputmethod.InputMethodManager; @@ -120,13 +121,12 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host { mApplier = new SyncRtSurfaceTransactionApplier(mViewRoot.mView); } if (mViewRoot.mView.isHardwareAccelerated()) { - mApplier.scheduleApply(false /* earlyWakeup */, params); + mApplier.scheduleApply(params); } else { // Window doesn't support hardware acceleration, no synchronization for now. // TODO(b/149342281): use mViewRoot.mSurface.getNextFrameNumber() to sync on every // frame instead. - mApplier.applyParams(new SurfaceControl.Transaction(), -1 /* frame */, - false /* earlyWakeup */, params); + mApplier.applyParams(new SurfaceControl.Transaction(), -1 /* frame */, params); } } @@ -229,4 +229,24 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host { } return mViewRoot.getTitle().toString(); } + + @Override + public int dipToPx(int dips) { + if (mViewRoot != null) { + return mViewRoot.dipToPx(dips); + } + return 0; + } + + @Override + public IBinder getWindowToken() { + if (mViewRoot == null) { + return null; + } + final View view = mViewRoot.getView(); + if (view == null) { + return null; + } + return view.getWindowToken(); + } } diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java index fb9d05e2d730c68b58ca3d151575f920b64720a6..792b974558bb9312bf7291d4e161d11c9c150117 100644 --- a/core/java/android/view/WindowInsetsAnimationController.java +++ b/core/java/android/view/WindowInsetsAnimationController.java @@ -181,4 +181,11 @@ public interface WindowInsetsAnimationController { * @return {@code true} if the instance is cancelled, {@code false} otherwise. */ boolean isCancelled(); + + /** + * @hide + * @return {@code true} when controller controls IME and IME has no insets (floating, + * fullscreen or non-overlapping). + */ + boolean hasZeroInsetsIme(); } diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 3d348efc7f0ff745116dc15813eadc88baf8bfa6..1a9003581078924c1a57646b80618ea200d70103 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -221,6 +221,13 @@ public interface WindowInsetsController { */ @Behavior int getSystemBarsBehavior(); + /** + * Disables or enables the animations. + * + * @hide + */ + void setAnimationsDisabled(boolean disable); + /** * @hide */ diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 76071278edf832fe3e3cce4799951137c4be8f34..5c6269421a1f6a1b93ea0fe1cf583a4e11d8e8ef 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1454,11 +1454,22 @@ public interface WindowManager extends ViewManager { @Deprecated public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000; - /** Window flag: When set, input method can't interact with the focusable window - * and can be placed to use more space and cover the input method. - * Note: When combined with {@link #FLAG_NOT_FOCUSABLE}, this flag has no - * effect since input method cannot interact with windows having {@link #FLAG_NOT_FOCUSABLE} - * flag set. + /** Window flag: when set, inverts the input method focusability of the window. + * + * The effect of setting this flag depends on whether {@link #FLAG_NOT_FOCUSABLE} is set: + *

+ * If {@link #FLAG_NOT_FOCUSABLE} is not set, i.e. when the window is focusable, + * setting this flag prevents this window from becoming the target of the input method. + * Consequently, it will not be able to interact with the input method, + * and will be layered above the input method (unless there is another input method + * target above it). + * + *

+ * If {@link #FLAG_NOT_FOCUSABLE} is set, setting this flag requests for the window + * to be the input method target even though the window is not focusable. + * Consequently, it will be layered below the input method. + * Note: Windows that set {@link #FLAG_NOT_FOCUSABLE} cannot interact with the input method, + * regardless of this flag. */ public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000; @@ -2142,13 +2153,12 @@ public interface WindowManager extends ViewManager { * focus. In particular, this checks the * {@link #FLAG_NOT_FOCUSABLE} and {@link #FLAG_ALT_FOCUSABLE_IM} * flags and returns true if the combination of the two corresponds - * to a window that needs to be behind the input method so that the - * user can type into it. + * to a window that can use the input method. * * @param flags The current window manager flags. * - * @return Returns {@code true} if such a window should be behind/interact - * with an input method, {@code false} if not. + * @return Returns {@code true} if a window with the given flags would be able to + * use the input method, {@code false} if not. */ public static boolean mayUseInputMethod(int flags) { return (flags & FLAG_NOT_FOCUSABLE) != FLAG_NOT_FOCUSABLE diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index a5ec4e90064145079e39068e73628c127d3b1c43..1af4c3636ac55e640c1242fdf30259cc43316cab 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -41,7 +41,6 @@ public class WindowlessWindowManager implements IWindowSession { private final static String TAG = "WindowlessWindowManager"; private class State { - //TODO : b/150190730 we should create it when view show and release it when view invisible. SurfaceControl mSurfaceControl; WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); int mDisplayId; @@ -137,7 +136,8 @@ public class WindowlessWindowManager implements IWindowSession { .setParent(mRootSurface) .setFormat(attrs.format) .setBufferSize(getSurfaceWidth(attrs), getSurfaceHeight(attrs)) - .setName(attrs.getTitle().toString()); + .setName(attrs.getTitle().toString()) + .setCallsite("WindowlessWindowManager.addToDisplay"); final SurfaceControl sc = b.build(); if (((attrs.inputFeatures & @@ -249,7 +249,7 @@ public class WindowlessWindowManager implements IWindowSession { if (viewFlags == View.VISIBLE) { t.setBufferSize(sc, getSurfaceWidth(attrs), getSurfaceHeight(attrs)) .setOpaque(sc, isOpaque(attrs)).show(sc).apply(); - outSurfaceControl.copyFrom(sc); + outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout"); } else { t.hide(sc).apply(); outSurfaceControl.release(); diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index eaaaa80f65ed83ba688aa3ff6e2ea98dd71c2a48..214da380ccdad1bcf0309544dd1ebe85ac985db1 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -635,7 +635,7 @@ public class AccessibilityNodeInfo implements Parcelable { "android.view.accessibility.extra.DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH"; /** - * Key used to request extra data for accessibility scanning tool's purposes. + * Key used to request extra data for the rendering information. * The key requests that a {@link AccessibilityNodeInfo.ExtraRenderingInfo} be added to this * info. This request is made with {@link #refreshWithExtraData(String, Bundle)} without * argument. @@ -5847,12 +5847,15 @@ public class AccessibilityNodeInfo implements Parcelable { } /** - * Gets the size object containing the height and the width of layout params if the node is - * a {@link ViewGroup} or a {@link TextView}, or null otherwise. Useful for accessibility - * scanning tool to understand whether the text is scalable and fits the view or not. + * Gets the size object containing the height and the width of + * {@link android.view.ViewGroup.LayoutParams} if the node is a {@link ViewGroup} or + * a {@link TextView}, or null otherwise. Useful for some accessibility services to + * understand whether the text is scalable and fits the view or not. * - * @return a {@link Size} stores layout height and layout width of the view, - * or null otherwise. + * @return a {@link Size} stores layout height and layout width of the view, or null + * otherwise. And the size value may be in pixels, + * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}, + * or {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} */ public @Nullable Size getLayoutSize() { return mLayoutSize; @@ -5870,9 +5873,9 @@ public class AccessibilityNodeInfo implements Parcelable { } /** - * Gets the text size if the node is a {@link TextView}, or -1 otherwise. Useful for - * accessibility scanning tool to understand whether the text is scalable and fits the view - * or not. + * Gets the text size if the node is a {@link TextView}, or -1 otherwise. Useful for some + * accessibility services to understand whether the text is scalable and fits the view or + * not. * * @return the text size of a {@code TextView}, or -1 otherwise. */ @@ -5893,7 +5896,7 @@ public class AccessibilityNodeInfo implements Parcelable { /** * Gets the text size unit if the node is a {@link TextView}, or -1 otherwise. * Text size returned from {@link #getTextSizeInPx} in raw pixels may scale by factors and - * convert from other units. Useful for accessibility scanning tool to understand whether + * convert from other units. Useful for some accessibility services to understand whether * the text is scalable and fits the view or not. * * @return the text size unit which type is {@link TypedValue#TYPE_DIMENSION} of a diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java index 9cdaba599d94ac5976f56d61c809c593eedff8e3..32b9cf7cdbb017b7f5e87d07d272d66d1a631e26 100644 --- a/core/java/android/view/autofill/AutofillId.java +++ b/core/java/android/view/autofill/AutofillId.java @@ -77,7 +77,10 @@ public final class AutofillId implements Parcelable { @TestApi public static AutofillId withoutSession(@NonNull AutofillId id) { final int flags = id.mFlags & ~FLAG_HAS_SESSION; - return new AutofillId(flags, id.mViewId, id.mVirtualLongId, NO_SESSION); + final long virtualChildId = + ((id.mFlags & FLAG_IS_VIRTUAL_LONG) != 0) ? id.mVirtualLongId + : id.mVirtualIntId; + return new AutofillId(flags, id.mViewId, virtualChildId, NO_SESSION); } /** @hide */ diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 3112039c36d8fe199f95c8e84426247a2a1ccc1a..fbfeda6f0bccf8ab71f83e71025dd8ee8dbdf08c 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -751,6 +751,8 @@ public final class AutofillManager { } } catch (RemoteException e) { Log.e(TAG, "Could not figure out if there was an autofill session", e); + } catch (SyncResultReceiver.TimeoutException e) { + Log.e(TAG, "Fail to get session restore status: " + e); } } } @@ -864,7 +866,9 @@ public final class AutofillManager { mService.getFillEventHistory(receiver); return receiver.getParcelableResult(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + Log.e(TAG, "Fail to get fill event history: " + e); return null; } } @@ -1477,10 +1481,13 @@ public final class AutofillManager { final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); try { - mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(), receiver); + mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(), + receiver); return receiver.getIntResult() == 1; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get enabled autofill services status."); } } @@ -1498,6 +1505,8 @@ public final class AutofillManager { return receiver.getParcelableResult(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get autofill services component name."); } } @@ -1522,8 +1531,9 @@ public final class AutofillManager { mService.getUserDataId(receiver); return receiver.getStringResult(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return null; + throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get user data id for field classification."); } } @@ -1544,8 +1554,9 @@ public final class AutofillManager { mService.getUserData(receiver); return receiver.getParcelableResult(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return null; + throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get user data for field classification."); } } @@ -1561,7 +1572,7 @@ public final class AutofillManager { try { mService.setUserData(userData); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -1583,8 +1594,9 @@ public final class AutofillManager { mService.isFieldClassificationEnabled(receiver); return receiver.getIntResult() == 1; } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return false; + throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get field classification enabled status."); } } @@ -1606,8 +1618,9 @@ public final class AutofillManager { mService.getDefaultFieldClassificationAlgorithm(receiver); return receiver.getStringResult(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return null; + throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get default field classification algorithm."); } } @@ -1627,8 +1640,9 @@ public final class AutofillManager { final String[] algorithms = receiver.getStringArrayResult(); return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return null; + throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get available field classification algorithms."); } } @@ -1651,6 +1665,8 @@ public final class AutofillManager { return receiver.getIntResult() == 1; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get autofill supported status."); } } @@ -2040,13 +2056,16 @@ public final class AutofillManager { } final SyncResultReceiver resultReceiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); - final int resultCode; + int resultCode; try { mService.setAugmentedAutofillWhitelist(toList(packages), toList(activities), resultReceiver); resultCode = resultReceiver.getIntResult(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + Log.e(TAG, "Fail to get the result of set AugmentedAutofill whitelist. " + e); + return; } switch (resultCode) { case RESULT_OK: @@ -2283,7 +2302,7 @@ public final class AutofillManager { // In theory, we could ignore this error since it's not a big deal, but // in reality, we rather crash the app anyways, as the failure could be // a consequence of something going wrong on the server side... - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -2661,7 +2680,7 @@ public final class AutofillManager { try { mService.onPendingSaveUi(operation, token); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + Log.e(TAG, "Error in onPendingSaveUi: ", e); } } } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 756ff78e5906e8165f09beed40e2e9cb391f7424..484b1c10423cf2154461a6418853da3e49cd5c69 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -487,6 +487,8 @@ public final class ContentCaptureManager { return resultReceiver.getParcelableResult(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get service componentName."); } } @@ -516,6 +518,9 @@ public final class ContentCaptureManager { return resultReceiver.getParcelableResult(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + Log.e(TAG, "Fail to get service settings componentName: " + e); + return null; } } @@ -567,9 +572,13 @@ public final class ContentCaptureManager { final SyncResultReceiver resultReceiver = syncRun( (r) -> mService.getContentCaptureConditions(mContext.getPackageName(), r)); - final ArrayList result = resultReceiver - .getParcelableListResult(); - return toSet(result); + try { + final ArrayList result = resultReceiver + .getParcelableListResult(); + return toSet(result); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get content capture conditions."); + } } /** @@ -639,15 +648,21 @@ public final class ContentCaptureManager { public boolean isContentCaptureFeatureEnabled() { final SyncResultReceiver resultReceiver = syncRun( (r) -> mService.isContentCaptureFeatureEnabled(r)); - final int resultCode = resultReceiver.getIntResult(); - switch (resultCode) { - case RESULT_CODE_TRUE: - return true; - case RESULT_CODE_FALSE: - return false; - default: - Log.wtf(TAG, "received invalid result: " + resultCode); - return false; + + try { + final int resultCode = resultReceiver.getIntResult(); + switch (resultCode) { + case RESULT_CODE_TRUE: + return true; + case RESULT_CODE_FALSE: + return false; + default: + Log.wtf(TAG, "received invalid result: " + resultCode); + return false; + } + } catch (SyncResultReceiver.TimeoutException e) { + Log.e(TAG, "Fail to get content capture feature enable status: " + e); + return false; } } @@ -663,7 +678,7 @@ public final class ContentCaptureManager { try { mService.removeData(request); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -691,7 +706,7 @@ public final class ContentCaptureManager { new DataShareAdapterDelegate(executor, dataShareWriteAdapter, mDataShareAdapterResourceManager)); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -709,10 +724,12 @@ public final class ContentCaptureManager { if (resultCode == RESULT_CODE_SECURITY_EXCEPTION) { throw new SecurityException(resultReceiver.getStringResult()); } - return resultReceiver; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); + } catch (SyncResultReceiver.TimeoutException e) { + throw new RuntimeException("Fail to get syn run result from SyncResultReceiver."); } + return resultReceiver; } /** @hide */ diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index 301ce9f013e429959584bfc33f16f046f40cbd07..3f5ef5a2651d3071de3d71039716776a1a0741cb 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -39,8 +39,8 @@ import com.android.internal.util.Preconditions; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.security.SecureRandom; import java.util.ArrayList; -import java.util.Random; /** * Session used when the Android a system-provided content capture service @@ -50,7 +50,9 @@ public abstract class ContentCaptureSession implements AutoCloseable { private static final String TAG = ContentCaptureSession.class.getSimpleName(); - private static final Random sIdGenerator = new Random(); + // TODO(b/158778794): to make the session ids truly globally unique across + // processes, we may need to explore other options. + private static final SecureRandom ID_GENERATOR = new SecureRandom(); /** * Initial state, when there is no session. @@ -622,7 +624,7 @@ public abstract class ContentCaptureSession implements AutoCloseable { private static int getRandomSessionId() { int id; do { - id = sIdGenerator.nextInt(); + id = ID_GENERATOR.nextInt(); } while (id == NO_SESSION_ID); return id; } diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 6eb71f747be6f28b66547798d3b41aefd2ed8e8c..c43beea98c7e21077299654b4133303b3bc14dd7 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -52,6 +52,7 @@ import android.view.contentcapture.ViewNode.ViewStructureImpl; import com.android.internal.os.IResultReceiver; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -146,7 +147,44 @@ public final class MainContentCaptureSession extends ContentCaptureSession { * Binder object used to update the session state. */ @NonNull - private final IResultReceiver.Stub mSessionStateReceiver; + private final SessionStateReceiver mSessionStateReceiver; + + private static class SessionStateReceiver extends IResultReceiver.Stub { + private final WeakReference mMainSession; + + SessionStateReceiver(MainContentCaptureSession session) { + mMainSession = new WeakReference<>(session); + } + + @Override + public void send(int resultCode, Bundle resultData) { + final MainContentCaptureSession mainSession = mMainSession.get(); + if (mainSession == null) { + Log.w(TAG, "received result after mina session released"); + return; + } + final IBinder binder; + if (resultData != null) { + // Change in content capture enabled. + final boolean hasEnabled = resultData.getBoolean(EXTRA_ENABLED_STATE); + if (hasEnabled) { + final boolean disabled = (resultCode == RESULT_CODE_FALSE); + mainSession.mDisabled.set(disabled); + return; + } + binder = resultData.getBinder(EXTRA_BINDER); + if (binder == null) { + Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result"); + mainSession.mHandler.post(() -> mainSession.resetSession( + STATE_DISABLED | STATE_INTERNAL_ERROR)); + return; + } + } else { + binder = null; + } + mainSession.mHandler.post(() -> mainSession.onSessionStarted(resultCode, binder)); + } + } protected MainContentCaptureSession(@NonNull Context context, @NonNull ContentCaptureManager manager, @NonNull Handler handler, @@ -159,32 +197,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { final int logHistorySize = mManager.mOptions.logHistorySize; mFlushHistory = logHistorySize > 0 ? new LocalLog(logHistorySize) : null; - mSessionStateReceiver = new IResultReceiver.Stub() { - @Override - public void send(int resultCode, Bundle resultData) { - final IBinder binder; - if (resultData != null) { - // Change in content capture enabled. - final boolean hasEnabled = resultData.getBoolean(EXTRA_ENABLED_STATE); - if (hasEnabled) { - final boolean disabled = (resultCode == RESULT_CODE_FALSE); - mDisabled.set(disabled); - return; - } - binder = resultData.getBinder(EXTRA_BINDER); - if (binder == null) { - Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result"); - mHandler.post(() -> resetSession( - STATE_DISABLED | STATE_INTERNAL_ERROR)); - return; - } - } else { - binder = null; - } - mHandler.post(() -> onSessionStarted(resultCode, binder)); - } - }; - + mSessionStateReceiver = new SessionStateReceiver(this); } @Override @@ -543,6 +556,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession { Log.e(TAG, "Error destroying system-service session " + mId + " for " + getDebugState() + ": " + e); } + + if (mDirectServiceInterface != null) { + mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0); + } + mDirectServiceInterface = null; } // TODO(b/122454205): once we support multiple sessions, we might need to move some of these diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java index e4ac5889a3c0d7f5ad5d127e26c8377d1ce63869..b8893cee834d5a58c99b02ecd70811f803d325d4 100644 --- a/core/java/android/view/inputmethod/InlineSuggestion.java +++ b/core/java/android/view/inputmethod/InlineSuggestion.java @@ -317,8 +317,24 @@ public final class InlineSuggestion implements Parcelable { */ @MainThread private void handleOnSurfacePackage(SurfaceControlViewHost.SurfacePackage surfacePackage) { + if (surfacePackage == null) { + return; + } + if (mSurfacePackage != null || mSurfacePackageConsumer == null) { + // The surface package is not consumed, release it immediately. + surfacePackage.release(); + try { + mInlineContentProvider.onSurfacePackageReleased(); + } catch (RemoteException e) { + Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e); + } + return; + } mSurfacePackage = surfacePackage; - if (mSurfacePackage != null && mSurfacePackageConsumer != null) { + if (mSurfacePackage == null) { + return; + } + if (mSurfacePackageConsumer != null) { mSurfacePackageConsumer.accept(mSurfacePackage); mSurfacePackageConsumer = null; } @@ -334,6 +350,10 @@ public final class InlineSuggestion implements Parcelable { } mSurfacePackage = null; } + // Clear the pending surface package consumer, if any. This can happen if the IME + // attaches the view to window and then quickly detaches it from the window, before + // the surface package requested upon attaching to window was returned. + mSurfacePackageConsumer = null; } @MainThread diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 71dd6653f6a65b99312b18e875479a0a10e493d9..37b352940ee42521d86ed7f43744a279437d7fae 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -19,6 +19,9 @@ package android.view.inputmethod; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; +import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_EDITOR; +import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_SAME_EDITOR; + import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; @@ -538,6 +541,19 @@ public final class InputMethodManager { return servedView.hasWindowFocus() || isAutofillUIShowing(servedView); } + /** + * Reports whether the IME is currently perceptible or not, according to the leash applied by + * {@link android.view.WindowInsetsController}. + * @hide + */ + public void reportPerceptible(IBinder windowToken, boolean perceptible) { + try { + mService.reportPerceptible(windowToken, perceptible); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private final class DelegateImpl implements ImeFocusController.InputMethodManagerDelegate { /** @@ -616,12 +632,23 @@ public final class InputMethodManager { // For some reason we didn't do a startInput + windowFocusGain, so // we'll just do a window focus gain and call it a day. try { - if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput"); + View servedView = controller.getServedView(); + boolean nextFocusSameEditor = servedView != null && servedView == focusedView + && isSameEditorAndAcceptingText(focusedView); + if (DEBUG) { + Log.v(TAG, "Reporting focus gain, without startInput" + + ", nextFocusIsServedView=" + nextFocusSameEditor); + } + final int startInputReason = + nextFocusSameEditor ? WINDOW_FOCUS_GAIN_REPORT_WITH_SAME_EDITOR + : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_EDITOR; mService.startInputOrWindowGainedFocus( - StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient, + startInputReason, mClient, focusedView.getWindowToken(), startInputFlags, softInputMode, windowFlags, - null, null, 0 /* missingMethodFlags */, + null, + null, + 0 /* missingMethodFlags */, mCurRootView.mContext.getApplicationInfo().targetSdkVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -645,11 +672,6 @@ public final class InputMethodManager { @Override public void setCurrentRootView(ViewRootImpl rootView) { synchronized (mH) { - if (mCurRootView != null) { - // Reset the last served view and restart window focus state of the root view. - mCurRootView.getImeFocusController().setServedView(null); - mRestartOnNextWindowFocus = true; - } mCurRootView = rootView; } } @@ -677,6 +699,37 @@ public final class InputMethodManager { } return result; } + + /** + * For {@link ImeFocusController} to check if the given focused view aligns with the same + * editor and the editor is active to accept the text input. + * + * TODO(b/160968797): Remove this method and move mCurrentTextBoxAttritube to + * ImeFocusController. + * In the long-term, we should make mCurrentTextBoxAtrtribue as per-window base instance, + * so that we we can directly check if the current focused view aligned with the same editor + * in the window without using this checking. + * + * Note that this method is only use for fixing start new input may ignored issue + * (e.g. b/160391516), DO NOT leverage this method to do another check. + */ + public boolean isSameEditorAndAcceptingText(View view) { + synchronized (mH) { + if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null) { + return false; + } + + final EditorInfo ic = mCurrentTextBoxAttribute; + // This sameEditor checking is based on using object hash comparison to check if + // some fields of the current EditorInfo (e.g. autoFillId, OpPackageName) the + // hash code is same as the given focused view. + final boolean sameEditor = view.onCheckIsTextEditor() && view.getId() == ic.fieldId + && view.getAutofillId() == ic.autofillId + && view.getContext().getOpPackageName() == ic.packageName; + return sameEditor && mServedInputConnectionWrapper != null + && mServedInputConnectionWrapper.isActive(); + } + } } /** @hide */ @@ -2072,28 +2125,36 @@ public final class InputMethodManager { /** * Call showSoftInput with currently focused view. - * @return {@code true} if IME can be shown. + * + * @param windowToken the window from which this request originates. If this doesn't match the + * currently served view, the request is ignored and returns {@code false}. + * + * @return {@code true} if IME can (eventually) be shown, {@code false} otherwise. * @hide */ - public boolean requestImeShow(ResultReceiver resultReceiver) { + public boolean requestImeShow(IBinder windowToken) { synchronized (mH) { final View servedView = getServedViewLocked(); - if (servedView == null) { + if (servedView == null || servedView.getWindowToken() != windowToken) { return false; } - showSoftInput(servedView, 0 /* flags */, resultReceiver); + showSoftInput(servedView, 0 /* flags */, null /* resultReceiver */); return true; } } /** * Notify IME directly that it is no longer visible. + * + * @param windowToken the window from which this request originates. If this doesn't match the + * currently served view, the request is ignored. * @hide */ - public void notifyImeHidden() { + public void notifyImeHidden(IBinder windowToken) { synchronized (mH) { try { - if (mCurMethod != null) { + if (mCurMethod != null && mCurRootView != null + && mCurRootView.getWindowToken() == windowToken) { mCurMethod.notifyImeHidden(); } } catch (RemoteException re) { @@ -2103,15 +2164,15 @@ public final class InputMethodManager { /** * Notify IME directly to remove surface as it is no longer visible. + * @param windowToken The client window token that requests the IME to remove its surface. * @hide */ - public void removeImeSurface() { + public void removeImeSurface(IBinder windowToken) { synchronized (mH) { try { - if (mCurMethod != null) { - mCurMethod.removeImeSurface(); - } - } catch (RemoteException re) { + mService.removeImeSurfaceFromWindow(windowToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } } diff --git a/core/java/android/view/textclassifier/TEST_MAPPING b/core/java/android/view/textclassifier/TEST_MAPPING index 01a6edecf21ecbe062ea483e988964e81738aae8..2f9e737dc213d6471e492fc03ac599d7ef3f08aa 100644 --- a/core/java/android/view/textclassifier/TEST_MAPPING +++ b/core/java/android/view/textclassifier/TEST_MAPPING @@ -10,6 +10,22 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsTextClassifierTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "TextClassifierServiceTest", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] } ] } diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 7042f29fc4e407fb21d5d53209dd3d3f232d4d5c..4a655117619897f84349b557154ee11449097201 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -205,6 +205,8 @@ public class WebChromeClient { *

Note that if the {@link WebChromeClient} is set to be {@code null}, * or if {@link WebChromeClient} is not set at all, the default dialog will * be suppressed and Javascript execution will continue immediately. + *

Note that the default dialog does not inherit the {@link + * android.view.Display#FLAG_SECURE} flag from the parent window. * * @param view The WebView that initiated the callback. * @param url The url of the page requesting the dialog. @@ -240,6 +242,8 @@ public class WebChromeClient { * or if {@link WebChromeClient} is not set at all, the default dialog will * be suppressed and the default value of {@code false} will be returned to * the JavaScript code immediately. + *

Note that the default dialog does not inherit the {@link + * android.view.Display#FLAG_SECURE} flag from the parent window. * * @param view The WebView that initiated the callback. * @param url The url of the page requesting the dialog. @@ -274,6 +278,8 @@ public class WebChromeClient { * or if {@link WebChromeClient} is not set at all, the default dialog will * be suppressed and {@code null} will be returned to the JavaScript code * immediately. + *

Note that the default dialog does not inherit the {@link + * android.view.Display#FLAG_SECURE} flag from the parent window. * * @param view The WebView that initiated the callback. * @param url The url of the page requesting the dialog. @@ -308,6 +314,8 @@ public class WebChromeClient { *

Note that if the {@link WebChromeClient} is set to be {@code null}, * or if {@link WebChromeClient} is not set at all, the default dialog will * be suppressed and the navigation will be resumed immediately. + *

Note that the default dialog does not inherit the {@link + * android.view.Display#FLAG_SECURE} flag from the parent window. * * @param view The WebView that initiated the callback. * @param url The url of the page requesting the dialog. diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 51d37a53f21f6adaefc7d1bcd94741f22f946453..07a721f5a9c9134af9d8bc109cf52efd64bc3e19 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -6354,6 +6354,8 @@ public class Editor { // The offsets of that last touch down event. Remembered to start selection there. private int mMinTouchOffset, mMaxTouchOffset; + private boolean mGestureStayedInTapRegion; + // Where the user first starts the drag motion. private int mStartOffset = -1; @@ -6460,8 +6462,10 @@ public class Editor { eventX, eventY); // Double tap detection - if (mTouchState.isMultiTapInSameArea() && (isMouse - || mTouchState.isOnHandle() || isPositionOnText(eventX, eventY))) { + if (mGestureStayedInTapRegion + && mTouchState.isMultiTapInSameArea() + && (isMouse || isPositionOnText(eventX, eventY) + || mTouchState.isOnHandle())) { if (TextView.DEBUG_CURSOR) { logCursor("SelectionModifierCursorController: onTouchEvent", "ACTION_DOWN: select and start drag"); @@ -6473,6 +6477,7 @@ public class Editor { } mDiscardNextActionUp = true; } + mGestureStayedInTapRegion = true; mHaventMovedEnoughToStartDrag = true; } break; @@ -6488,6 +6493,14 @@ public class Editor { break; case MotionEvent.ACTION_MOVE: + if (mGestureStayedInTapRegion) { + final ViewConfiguration viewConfig = ViewConfiguration.get( + mTextView.getContext()); + mGestureStayedInTapRegion = EditorTouchState.isDistanceWithin( + mTouchState.getLastDownX(), mTouchState.getLastDownY(), + eventX, eventY, viewConfig.getScaledDoubleTapTouchSlop()); + } + if (mHaventMovedEnoughToStartDrag) { mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag(); } diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java index ff3ac0732aa210665fb558b580049204f61214b3..9eb63087a66eb3b8353b7a01f2ab50ba829edd44 100644 --- a/core/java/android/widget/EditorTouchState.java +++ b/core/java/android/widget/EditorTouchState.java @@ -174,12 +174,9 @@ public class EditorTouchState { int touchSlop = config.getScaledTouchSlop(); mMovedEnoughForDrag = distanceSquared > touchSlop * touchSlop; if (mMovedEnoughForDrag) { - // If the direction of the swipe motion is within 30 degrees of vertical, it is - // considered a vertical drag. We don't actually have to compute the angle to - // implement the check though. When the angle is exactly 30 degrees from - // vertical, 2*deltaX = distance. When the angle is less than 30 degrees from - // vertical, 2*deltaX < distance. - mIsDragCloseToVertical = (4 * deltaXSquared) <= distanceSquared; + // If the direction of the swipe motion is within 45 degrees of vertical, it is + // considered a vertical drag. + mIsDragCloseToVertical = Math.abs(deltaX) <= Math.abs(deltaY); } } } else if (action == MotionEvent.ACTION_CANCEL) { diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 8ea824d3ce82e74e00beeb1289badfe7d165202b..7fa8f9a315265cfa76ecabc89aabc5c019f27d23 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -1000,6 +1000,7 @@ public final class Magnifier { .setName("magnifier surface") .setFlags(SurfaceControl.HIDDEN) .setParent(parentSurfaceControl) + .setCallsite("InternalPopupWindow") .build(); mSurface = new Surface(); mSurface.copyFrom(mSurfaceControl); diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index e07181abe19c8044083b8f7007d719500cbbd37d..843700cef55ecdd1fe8739c9c14ddcef3a17eb30 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -971,7 +971,8 @@ public final class SelectionActionModeHelper { return new TextClassifierEvent.LanguageDetectionEvent.Builder(eventType) .setEventContext(classificationContext) .setResultId(classification.getId()) - .setEntityTypes(language) + // b/158481016: Disable language logging. + //.setEntityTypes(language) .setScores(score) .setActionIndices(classification.getActions().indexOf(translateAction)) .setModelName(model) diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java index 2679c69be4f61c693d4b679da8d3cd386bfa9ffe..fb5d55dd21419a1633ead16d8a305224d8d84e02 100644 --- a/core/java/android/widget/ToastPresenter.java +++ b/core/java/android/widget/ToastPresenter.java @@ -27,7 +27,6 @@ import android.content.res.Resources; import android.graphics.PixelFormat; import android.os.IBinder; import android.os.RemoteException; -import android.os.UserHandle; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; @@ -90,7 +89,7 @@ public class ToastPresenter { // context to it. This is problematic for multi-user because callers can pass a context // created via Context.createContextAsUser(). mAccessibilityManager = new AccessibilityManager(context, accessibilityManager, - UserHandle.getCallingUserId()); + context.getUserId()); mParams = createLayoutParams(); } diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java index 8657e828a3f619a4d767ffdbcef1dd4457a89a70..9712311aab7c40c5292b64341157c337f8697200 100644 --- a/core/java/android/widget/inline/InlineContentView.java +++ b/core/java/android/widget/inline/InlineContentView.java @@ -18,16 +18,22 @@ package android.widget.inline; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.Context; import android.graphics.PixelFormat; +import android.graphics.PointF; +import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; +import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import java.lang.ref.WeakReference; import java.util.function.Consumer; /** @@ -86,8 +92,10 @@ public class InlineContentView extends ViewGroup { * * @hide */ + @TestApi public interface SurfacePackageUpdater { + /** * Called when the previous surface package is released due to view being detached * from the window. @@ -99,14 +107,16 @@ public class InlineContentView extends ViewGroup { * * @param consumer consumes the updated surface package. */ - void getSurfacePackage(Consumer consumer); + void getSurfacePackage(@NonNull Consumer consumer); } @NonNull private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() { @Override public void surfaceCreated(@NonNull SurfaceHolder holder) { - mSurfaceControlCallback.onCreated(mSurfaceView.getSurfaceControl()); + final SurfaceControl surfaceControl = mSurfaceView.getSurfaceControl(); + surfaceControl.addOnReparentListener(mOnReparentListener); + mSurfaceControlCallback.onCreated(surfaceControl); } @Override @@ -117,13 +127,52 @@ public class InlineContentView extends ViewGroup { @Override public void surfaceDestroyed(@NonNull SurfaceHolder holder) { - mSurfaceControlCallback.onDestroyed(mSurfaceView.getSurfaceControl()); + final SurfaceControl surfaceControl = mSurfaceView.getSurfaceControl(); + surfaceControl.removeOnReparentListener(mOnReparentListener); + mSurfaceControlCallback.onDestroyed(surfaceControl); + } + }; + + @NonNull + private final SurfaceControl.OnReparentListener mOnReparentListener = + new SurfaceControl.OnReparentListener() { + @Override + public void onReparent(SurfaceControl.Transaction transaction, + SurfaceControl parent) { + final View parentSurfaceOwnerView = (parent != null) + ? parent.getLocalOwnerView() : null; + if (parentSurfaceOwnerView instanceof SurfaceView) { + mParentSurfaceOwnerView = new WeakReference<>( + (SurfaceView) parentSurfaceOwnerView); + } else { + mParentSurfaceOwnerView = null; + } + } + }; + + @NonNull + private final ViewTreeObserver.OnDrawListener mOnDrawListener = + new ViewTreeObserver.OnDrawListener() { + @Override + public void onDraw() { + computeParentPositionAndScale(); + final int visibility = InlineContentView.this.isShown() ? VISIBLE : GONE; + mSurfaceView.setVisibility(visibility); } }; @NonNull private final SurfaceView mSurfaceView; + @Nullable + private WeakReference mParentSurfaceOwnerView; + + @Nullable + private int[] mParentPosition; + + @Nullable + private PointF mParentScale; + @Nullable private SurfaceControlCallback mSurfaceControlCallback; @@ -153,6 +202,7 @@ public class InlineContentView extends ViewGroup { public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); + mSurfaceView.setEnableSurfaceClipping(true); } /** @@ -166,6 +216,12 @@ public class InlineContentView extends ViewGroup { return mSurfaceView.getSurfaceControl(); } + @Override + public void setClipBounds(Rect clipBounds) { + super.setClipBounds(clipBounds); + mSurfaceView.setClipBounds(clipBounds); + } + /** * @inheritDoc * @hide @@ -173,10 +229,33 @@ public class InlineContentView extends ViewGroup { public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mSurfaceView = new SurfaceView(context, attrs, defStyleAttr, defStyleRes); + mSurfaceView = new SurfaceView(context, attrs, defStyleAttr, defStyleRes) { + @Override + protected void onSetSurfacePositionAndScaleRT( + @NonNull SurfaceControl.Transaction transaction, + @NonNull SurfaceControl surface, int positionLeft, int positionTop, + float postScaleX, float postScaleY) { + // If we have a parent position, we need to make our coordinates relative + // to the parent in the rendering space. + if (mParentPosition != null) { + positionLeft = (int) ((positionLeft - mParentPosition[0]) / mParentScale.x); + positionTop = (int) ((positionTop - mParentPosition[1]) / mParentScale.y); + } + + // Any scaling done to the parent or its predecessors would be applied + // via the surfaces parent -> child relation, so we only propagate any + // scaling set on the InlineContentView itself. + postScaleX = InlineContentView.this.getScaleX(); + postScaleY = InlineContentView.this.getScaleY(); + + super.onSetSurfacePositionAndScaleRT(transaction, surface, positionLeft, + positionTop, postScaleX, postScaleY); + } + }; mSurfaceView.setZOrderOnTop(true); mSurfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT); addView(mSurfaceView); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } /** @@ -184,6 +263,7 @@ public class InlineContentView extends ViewGroup { * * @hide */ + @TestApi public void setChildSurfacePackageUpdater( @Nullable SurfacePackageUpdater surfacePackageUpdater) { mSurfacePackageUpdater = surfacePackageUpdater; @@ -197,9 +277,14 @@ public class InlineContentView extends ViewGroup { mSurfacePackageUpdater.getSurfacePackage( sp -> { if (DEBUG) Log.v(TAG, "Received new SurfacePackage"); - mSurfaceView.setChildSurfacePackage(sp); + if (getViewRootImpl() != null) { + mSurfaceView.setChildSurfacePackage(sp); + } }); } + + mSurfaceView.setVisibility(getVisibility()); + getViewTreeObserver().addOnDrawListener(mOnDrawListener); } @Override @@ -209,6 +294,9 @@ public class InlineContentView extends ViewGroup { if (mSurfacePackageUpdater != null) { mSurfacePackageUpdater.onSurfacePackageReleased(); } + + getViewTreeObserver().removeOnDrawListener(mOnDrawListener); + mSurfaceView.setVisibility(View.GONE); } @Override @@ -255,4 +343,67 @@ public class InlineContentView extends ViewGroup { public boolean setZOrderedOnTop(boolean onTop) { return mSurfaceView.setZOrderedOnTop(onTop, /*allowDynamicChange*/ true); } + + + private void computeParentPositionAndScale() { + boolean contentPositionOrScaleChanged = false; + + // This method can be called on the UI or render thread but for the cases + // it is called these threads are not running concurrently, so no need to lock. + final SurfaceView parentSurfaceOwnerView = (mParentSurfaceOwnerView != null) + ? mParentSurfaceOwnerView.get() : null; + + if (parentSurfaceOwnerView != null) { + if (mParentPosition == null) { + mParentPosition = new int[2]; + } + final int oldParentPositionX = mParentPosition[0]; + final int oldParentPositionY = mParentPosition[1]; + parentSurfaceOwnerView.getLocationInSurface(mParentPosition); + if (oldParentPositionX != mParentPosition[0] + || oldParentPositionY != mParentPosition[1]) { + contentPositionOrScaleChanged = true; + } + + if (mParentScale == null) { + mParentScale = new PointF(); + } + + final float lastParentSurfaceWidth = parentSurfaceOwnerView + .getSurfaceRenderPosition().width(); + final float oldParentScaleX = mParentScale.x; + if (lastParentSurfaceWidth > 0) { + mParentScale.x = lastParentSurfaceWidth / + (float) parentSurfaceOwnerView.getWidth(); + } else { + mParentScale.x = 1.0f; + } + if (!contentPositionOrScaleChanged + && Float.compare(oldParentScaleX, mParentScale.x) != 0) { + contentPositionOrScaleChanged = true; + } + + final float lastParentSurfaceHeight = parentSurfaceOwnerView + .getSurfaceRenderPosition().height(); + final float oldParentScaleY = mParentScale.y; + if (lastParentSurfaceHeight > 0) { + mParentScale.y = lastParentSurfaceHeight + / (float) parentSurfaceOwnerView.getHeight(); + } else { + mParentScale.y = 1.0f; + } + if (!contentPositionOrScaleChanged + && Float.compare(oldParentScaleY, mParentScale.y) != 0) { + contentPositionOrScaleChanged = true; + } + } else if (mParentPosition != null || mParentScale != null) { + contentPositionOrScaleChanged = true; + mParentPosition = null; + mParentScale = null; + } + + if (contentPositionOrScaleChanged) { + mSurfaceView.requestUpdateSurfacePositionAndScale(); + } + } } diff --git a/core/java/android/window/TaskEmbedder.java b/core/java/android/window/TaskEmbedder.java index ca6c568c2668c784d6f5a733b6c7d07b72fe354d..0687a037df32a48828baca5c90529052eab98310 100644 --- a/core/java/android/window/TaskEmbedder.java +++ b/core/java/android/window/TaskEmbedder.java @@ -164,6 +164,7 @@ public abstract class TaskEmbedder { .setContainerLayer() .setParent(parent) .setName(name) + .setCallsite("TaskEmbedder.initialize") .build(); if (!onInitialize()) { diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java index d2614da31ff9d8514f6680ea065c6cf09c7dead1..9ccb4c172158d5678523dc339f55e051ae97b88f 100644 --- a/core/java/android/window/VirtualDisplayTaskEmbedder.java +++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java @@ -365,8 +365,8 @@ public class VirtualDisplayTaskEmbedder extends TaskEmbedder { // Found the topmost stack on target display. Now check if the topmost task's // description changed. if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { - mHost.onTaskBackgroundColorChanged(VirtualDisplayTaskEmbedder.this, - taskInfo.taskDescription.getBackgroundColor()); + mHost.post(()-> mHost.onTaskBackgroundColorChanged(VirtualDisplayTaskEmbedder.this, + taskInfo.taskDescription.getBackgroundColor())); } } diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 1b7517840650c94f386246009e48917d7404d6a6..508deacb49d7a8c105be51f48c2a46cafe6a2100 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -23,6 +23,7 @@ import static com.android.internal.accessibility.common.ShortcutConstants.Shortc import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView; import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets; import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets; +import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted; import android.annotation.Nullable; import android.app.Activity; @@ -61,18 +62,8 @@ public class AccessibilityShortcutChooserActivity extends Activity { } mTargets.addAll(getTargets(this, mShortcutType)); - - final String selectDialogTitle = - getString(R.string.accessibility_select_shortcut_menu_title); mTargetAdapter = new ShortcutTargetAdapter(mTargets); - mMenuDialog = new AlertDialog.Builder(this) - .setTitle(selectDialogTitle) - .setAdapter(mTargetAdapter, /* listener= */ null) - .setPositiveButton( - getString(R.string.edit_accessibility_shortcut_menu_button), - /* listener= */ null) - .setOnDismissListener(dialog -> finish()) - .create(); + mMenuDialog = createMenuDialog(); mMenuDialog.setOnShowListener(dialog -> updateDialogListeners()); mMenuDialog.show(); } @@ -154,4 +145,22 @@ public class AccessibilityShortcutChooserActivity extends Activity { mMenuDialog.getListView().setOnItemClickListener( isEditMenuMode ? this::onTargetChecked : this::onTargetSelected); } + + private AlertDialog createMenuDialog() { + final String dialogTitle = + getString(R.string.accessibility_select_shortcut_menu_title); + + final AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(dialogTitle) + .setAdapter(mTargetAdapter, /* listener= */ null) + .setOnDismissListener(dialog -> finish()); + + if (isUserSetupCompleted(this)) { + final String positiveButtonText = + getString(R.string.edit_accessibility_shortcut_menu_button); + builder.setPositiveButton(positiveButtonText, /* listener= */ null); + } + + return builder.create(); + } } diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java index 9ee0b0ea18910d62a9a26d822bff249a9bf66343..4b4e20f9181b5df4c4d0adc13ef8f26b21f102d4 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java @@ -156,4 +156,16 @@ public final class AccessibilityUtils { return false; } + + /** + * Indicates whether the current user has completed setup via the setup wizard. + * {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE} + * + * @return {@code true} if the setup is completed. + */ + public static boolean isUserSetupCompleted(Context context) { + return Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, /* def= */ 0, UserHandle.USER_CURRENT) + != /* false */ 0; + } } diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index 493865ac563f905da4c8705dac05f35340d1d106..b723db28782320f05515a42b0f7b98bc4f100950 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -151,6 +151,13 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { mOnProfileSelectedListener.onProfileSelected(position); } } + + @Override + public void onPageScrollStateChanged(int state) { + if (mOnProfileSelectedListener != null) { + mOnProfileSelectedListener.onProfilePageStateChanged(state); + } + } }); viewPager.setAdapter(this); viewPager.setCurrentItem(mCurrentPage); @@ -606,6 +613,17 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * {@link #PROFILE_WORK} if the work profile was selected. */ void onProfileSelected(int profileIndex); + + + /** + * Callback for when the scroll state changes. Useful for discovering when the user begins + * dragging, when the pager is automatically settling to the current page, or when it is + * fully stopped/idle. + * @param state {@link ViewPager#SCROLL_STATE_IDLE}, {@link ViewPager#SCROLL_STATE_DRAGGING} + * or {@link ViewPager#SCROLL_STATE_SETTLING} + * @see ViewPager.OnPageChangeListener#onPageScrollStateChanged + */ + void onProfilePageStateChanged(int state); } /** diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 5533e1eda52d35b593056437719160074a286f23..14cf258f18ab29495335a0f8906dde8d65dabeaa 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -102,6 +102,7 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.Button; @@ -129,6 +130,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.GridLayoutManager; import com.android.internal.widget.RecyclerView; import com.android.internal.widget.ResolverDrawerLayout; +import com.android.internal.widget.ViewPager; import com.google.android.collect.Lists; @@ -193,6 +195,7 @@ public class ChooserActivity extends ResolverActivity implements private boolean mIsAppPredictorComponentAvailable; private Map mDirectShareAppTargetCache; private Map mDirectShareShortcutInfoCache; + private Map mChooserTargetComponentNameCache; public static final int TARGET_TYPE_DEFAULT = 0; public static final int TARGET_TYPE_CHOOSER_TARGET = 1; @@ -204,6 +207,10 @@ public class ChooserActivity extends ResolverActivity implements public static final int SELECTION_TYPE_STANDARD = 3; public static final int SELECTION_TYPE_COPY = 4; + private static final int SCROLL_STATUS_IDLE = 0; + private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1; + private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2; + // statsd logger wrapper protected ChooserActivityLogger mChooserActivityLogger; @@ -293,6 +300,7 @@ public class ChooserActivity extends ResolverActivity implements protected MetricsLogger mMetricsLogger; private ContentPreviewCoordinator mPreviewCoord; + private int mScrollStatus = SCROLL_STATUS_IDLE; @VisibleForTesting protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter; @@ -504,6 +512,11 @@ public class ChooserActivity extends ResolverActivity implements adapterForUserHandle.addServiceResults(sri.originalTarget, sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET, /* directShareShortcutInfoCache */ null, mServiceConnections); + if (!sri.resultTargets.isEmpty() && sri.originalTarget != null) { + mChooserTargetComponentNameCache.put( + sri.resultTargets.get(0).getComponentName(), + sri.originalTarget.getResolvedComponentName()); + } } } unbindService(sri.connection); @@ -678,8 +691,14 @@ public class ChooserActivity extends ResolverActivity implements mPinnedSharedPrefs = getPinnedSharedPrefs(this); pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS); + + + // Exclude out Nearby from main list if chip is present, to avoid duplication + ComponentName nearbySharingComponent = getNearbySharingComponent(); + boolean hasNearby = nearbySharingComponent != null; + if (pa != null) { - ComponentName[] names = new ComponentName[pa.length]; + ComponentName[] names = new ComponentName[pa.length + (hasNearby ? 1 : 0)]; for (int i = 0; i < pa.length; i++) { if (!(pa[i] instanceof ComponentName)) { Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]); @@ -688,7 +707,14 @@ public class ChooserActivity extends ResolverActivity implements } names[i] = (ComponentName) pa[i]; } + if (hasNearby) { + names[names.length - 1] = nearbySharingComponent; + } + mFilteredComponentNames = names; + } else if (hasNearby) { + mFilteredComponentNames = new ComponentName[1]; + mFilteredComponentNames[0] = nearbySharingComponent; } pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS); @@ -765,6 +791,7 @@ public class ChooserActivity extends ResolverActivity implements target.getAction() ); mDirectShareShortcutInfoCache = new HashMap<>(); + mChooserTargetComponentNameCache = new HashMap<>(); } @Override @@ -786,17 +813,15 @@ public class ChooserActivity extends ResolverActivity implements private AppPredictor.Callback createAppPredictorCallback( ChooserListAdapter chooserListAdapter) { return resultList -> { - //TODO(arangelov) Take care of edge case when callback called after swiping tabs if (isFinishing() || isDestroyed()) { return; } if (chooserListAdapter.getCount() == 0) { return; } - if (resultList.isEmpty()) { + if (resultList.isEmpty() + && shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) { // APS may be disabled, so try querying targets ourselves. - //TODO(arangelov) queryDirectShareTargets indirectly uses mIntents. - // Investigate implications for work tab. queryDirectShareTargets(chooserListAdapter, true); return; } @@ -993,11 +1018,17 @@ public class ChooserActivity extends ResolverActivity implements /** * Update UI to reflect changes in data. - *

If {@code listAdapter} is {@code null}, both profile list adapters are updated. + *

If {@code listAdapter} is {@code null}, both profile list adapters are updated if + * available. */ private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) { + // Refresh pinned items + mPinnedSharedPrefs = getPinnedSharedPrefs(this); if (listAdapter == null) { mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); + if (mChooserMultiProfilePagerAdapter.getCount() > 1) { + mChooserMultiProfilePagerAdapter.getInactiveListAdapter().handlePackagesChanged(); + } } else { listAdapter.handlePackagesChanged(); } @@ -1059,6 +1090,10 @@ public class ChooserActivity extends ResolverActivity implements @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + ViewPager viewPager = findViewById(R.id.profile_pager); + if (shouldShowTabs() && viewPager.isLayoutRtl()) { + mMultiProfilePagerAdapter.setupViewPager(viewPager); + } mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation); adjustPreviewWidth(newConfig.orientation, null); @@ -1117,7 +1152,7 @@ public class ChooserActivity extends ResolverActivity implements final ComponentName cn = getNearbySharingComponent(); if (cn == null) return null; - final Intent resolveIntent = new Intent(); + final Intent resolveIntent = new Intent(originalIntent); resolveIntent.setComponent(cn); final ResolveInfo ri = getPackageManager().resolveActivity( resolveIntent, PackageManager.GET_META_DATA); @@ -1281,6 +1316,12 @@ public class ChooserActivity extends ResolverActivity implements ViewGroup parent) { ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate( R.layout.chooser_grid_preview_image, parent, false); + + final ViewGroup actionRow = + (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row); + //TODO: addActionButton(actionRow, createCopyButton()); + addActionButton(actionRow, createNearbyButton(targetIntent)); + mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true); String action = targetIntent.getAction(); @@ -1391,10 +1432,11 @@ public class ChooserActivity extends ResolverActivity implements ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate( R.layout.chooser_grid_preview_file, parent, false); - // TODO(b/120417119): Disable file copy until after moving to sysui, - // due to permissions issues - //((ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row)) - // .addView(createCopyButton()); + final ViewGroup actionRow = + (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row); + //TODO(b/120417119): addActionButton(actionRow, createCopyButton()); + addActionButton(actionRow, createNearbyButton(targetIntent)); + String action = targetIntent.getAction(); if (Intent.ACTION_SEND.equals(action)) { @@ -1802,7 +1844,8 @@ public class ChooserActivity extends ResolverActivity implements } } - void queryTargetServices(ChooserListAdapter adapter) { + @VisibleForTesting + protected void queryTargetServices(ChooserListAdapter adapter) { mQueriedTargetServicesTimeMs = System.currentTimeMillis(); Context selectedProfileContext = createContextAsUser( @@ -1955,7 +1998,8 @@ public class ChooserActivity extends ResolverActivity implements return driList; } - private void queryDirectShareTargets( + @VisibleForTesting + protected void queryDirectShareTargets( ChooserListAdapter adapter, boolean skipAppPredictionService) { mQueriedSharingShortcutsTimeMs = System.currentTimeMillis(); UserHandle userHandle = adapter.getUserHandle(); @@ -1967,7 +2011,6 @@ public class ChooserActivity extends ResolverActivity implements } } // Default to just querying ShortcutManager if AppPredictor not present. - //TODO(arangelov) we're using mIntents here, investicate possible implications on work tab final IntentFilter filter = getTargetIntentFilter(); if (filter == null) { return; @@ -1983,6 +2026,29 @@ public class ChooserActivity extends ResolverActivity implements }); } + /** + * Returns {@code false} if {@code userHandle} is the work profile and it's either + * in quiet mode or not running. + */ + private boolean shouldQueryShortcutManager(UserHandle userHandle) { + if (!shouldShowTabs()) { + return true; + } + if (!getWorkProfileUserHandle().equals(userHandle)) { + return true; + } + if (!isUserRunning(userHandle)) { + return false; + } + if (!isUserUnlocked(userHandle)) { + return false; + } + if (isQuietModeEnabled(userHandle)) { + return false; + } + return true; + } + private void sendChooserTargetRankingScore(List chooserTargetScores, UserHandle userHandle) { final Message msg = Message.obtain(); @@ -2182,6 +2248,9 @@ public class ChooserActivity extends ResolverActivity implements } void updateModelAndChooserCounts(TargetInfo info) { + if (info != null && info instanceof MultiDisplayResolveInfo) { + info = ((MultiDisplayResolveInfo) info).getSelectedTarget(); + } if (info != null) { sendClickToAppPredictor(info); final ResolveInfo ri = info.getResolveInfo(); @@ -2223,15 +2292,18 @@ public class ChooserActivity extends ResolverActivity implements List targetIds = new ArrayList<>(); for (ChooserTargetInfo chooserTargetInfo : surfacedTargetInfo) { ChooserTarget chooserTarget = chooserTargetInfo.getChooserTarget(); - String componentName = chooserTarget.getComponentName().flattenToString(); + ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault( + chooserTarget.getComponentName(), chooserTarget.getComponentName()); if (mDirectShareShortcutInfoCache.containsKey(chooserTarget)) { String shortcutId = mDirectShareShortcutInfoCache.get(chooserTarget).getId(); targetIds.add(new AppTargetId( - String.format("%s/%s/%s", shortcutId, componentName, SHORTCUT_TARGET))); + String.format("%s/%s/%s", shortcutId, componentName.flattenToString(), + SHORTCUT_TARGET))); } else { String titleHash = ChooserUtil.md5(chooserTarget.getTitle().toString()); targetIds.add(new AppTargetId( - String.format("%s/%s/%s", titleHash, componentName, CHOOSER_TARGET))); + String.format("%s/%s/%s", titleHash, componentName.flattenToString(), + CHOOSER_TARGET))); } } directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds); @@ -2253,7 +2325,8 @@ public class ChooserActivity extends ResolverActivity implements } if (mChooserTargetRankingEnabled && appTarget == null) { // Send ChooserTarget sharing info to AppPredictor. - ComponentName componentName = chooserTarget.getComponentName(); + ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault( + chooserTarget.getComponentName(), chooserTarget.getComponentName()); try { appTarget = new AppTarget.Builder( new AppTargetId(componentName.flattenToString()), @@ -2644,6 +2717,7 @@ public class ChooserActivity extends ResolverActivity implements if (recyclerView.getVisibility() == View.VISIBLE) { int directShareHeight = 0; rowsToShow = Math.min(4, rowsToShow); + boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow); mLastNumberOfChildren = recyclerView.getChildCount(); for (int i = 0, childCount = recyclerView.getChildCount(); i < childCount && rowsToShow > 0; i++) { @@ -2654,6 +2728,9 @@ public class ChooserActivity extends ResolverActivity implements } int height = child.getHeight(); offset += height; + if (shouldShowExtraRow) { + offset += height; + } if (gridAdapter.getTargetType( recyclerView.getChildAdapterPosition(child)) @@ -2677,7 +2754,7 @@ public class ChooserActivity extends ResolverActivity implements offset = Math.min(offset, minHeight); } } else { - ViewGroup currentEmptyStateView = getCurrentEmptyStateView(); + ViewGroup currentEmptyStateView = getActiveEmptyStateView(); if (currentEmptyStateView.getVisibility() == View.VISIBLE) { offset += currentEmptyStateView.getHeight(); } @@ -2688,6 +2765,18 @@ public class ChooserActivity extends ResolverActivity implements } } + /** + * If we have a tabbed view and are showing 1 row in the current profile and an empty + * state screen in the other profile, to prevent cropping of the empty state screen we show + * a second row in the current profile. + */ + private boolean shouldShowExtraRow(int rowsToShow) { + return shouldShowTabs() + && rowsToShow == 1 + && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen( + mChooserMultiProfilePagerAdapter.getInactiveListAdapter()); + } + /** * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle * does not match either the personal or work user handle. @@ -2702,7 +2791,7 @@ public class ChooserActivity extends ResolverActivity implements return -1; } - private ViewGroup getCurrentEmptyStateView() { + private ViewGroup getActiveEmptyStateView() { int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage(); return mChooserMultiProfilePagerAdapter.getItem(currentPage).getEmptyStateView(); } @@ -2765,17 +2854,7 @@ public class ChooserActivity extends ResolverActivity implements || chooserListAdapter.mDisplayList.isEmpty()) { chooserListAdapter.notifyDataSetChanged(); } else { - new AsyncTask() { - @Override - protected Void doInBackground(Void... voids) { - chooserListAdapter.updateAlphabeticalList(); - return null; - } - @Override - protected void onPostExecute(Void aVoid) { - chooserListAdapter.notifyDataSetChanged(); - } - }.execute(); + chooserListAdapter.updateAlphabeticalList(); } // don't support direct share on low ram devices @@ -2784,6 +2863,12 @@ public class ChooserActivity extends ResolverActivity implements return; } + // no need to query direct share for work profile when its locked or disabled + if (!shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) { + getChooserActivityLogger().logSharesheetAppLoadComplete(); + return; + } + if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS || ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { if (DEBUG) { @@ -2803,6 +2888,24 @@ public class ChooserActivity extends ResolverActivity implements getChooserActivityLogger().logSharesheetAppLoadComplete(); } + @VisibleForTesting + protected boolean isUserRunning(UserHandle userHandle) { + UserManager userManager = getSystemService(UserManager.class); + return userManager.isUserRunning(userHandle); + } + + @VisibleForTesting + protected boolean isUserUnlocked(UserHandle userHandle) { + UserManager userManager = getSystemService(UserManager.class); + return userManager.isUserUnlocked(userHandle); + } + + @VisibleForTesting + protected boolean isQuietModeEnabled(UserHandle userHandle) { + UserManager userManager = getSystemService(UserManager.class); + return userManager.isQuietModeEnabled(userHandle); + } + private void setupScrollListener() { if (mResolverDrawerLayout == null) { return; @@ -2812,10 +2915,20 @@ public class ChooserActivity extends ResolverActivity implements final float defaultElevation = elevatedView.getElevation(); final float chooserHeaderScrollElevation = getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation); - mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener( new RecyclerView.OnScrollListener() { public void onScrollStateChanged(RecyclerView view, int scrollState) { + if (scrollState == RecyclerView.SCROLL_STATE_IDLE) { + if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) { + mScrollStatus = SCROLL_STATUS_IDLE; + setHorizontalScrollingEnabled(true); + } + } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) { + if (mScrollStatus == SCROLL_STATUS_IDLE) { + mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL; + setHorizontalScrollingEnabled(false); + } + } } public void onScrolled(RecyclerView view, int dx, int dy) { @@ -3016,6 +3129,44 @@ public class ChooserActivity extends ResolverActivity implements currentRootAdapter.updateDirectShareExpansion(); } + @Override + protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + if (shouldShowTabs()) { + mChooserMultiProfilePagerAdapter + .setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom()); + mChooserMultiProfilePagerAdapter.setupContainerPadding( + getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container)); + } + return super.onApplyWindowInsets(v, insets); + } + + private void setHorizontalScrollingEnabled(boolean enabled) { + ResolverViewPager viewPager = findViewById(R.id.profile_pager); + viewPager.setSwipingEnabled(enabled); + } + + private void setVerticalScrollEnabled(boolean enabled) { + ChooserGridLayoutManager layoutManager = + (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView() + .getLayoutManager(); + layoutManager.setVerticalScrollEnabled(enabled); + } + + @Override + void onHorizontalSwipeStateChanged(int state) { + if (state == ViewPager.SCROLL_STATE_DRAGGING) { + if (mScrollStatus == SCROLL_STATUS_IDLE) { + mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL; + setVerticalScrollEnabled(false); + } + } else if (state == ViewPager.SCROLL_STATE_IDLE) { + if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) { + mScrollStatus = SCROLL_STATUS_IDLE; + setVerticalScrollEnabled(true); + } + } + } + /** * Adapter for all types of items and targets in ShareSheet. * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the @@ -3506,10 +3657,9 @@ public class ChooserActivity extends ResolverActivity implements * Only expand direct share area if there is a minimum number of targets. */ private boolean canExpandDirectShare() { - int orientation = getResources().getConfiguration().orientation; - return mChooserListAdapter.getNumServiceTargetsForExpand() > getMaxTargetsPerRow() - && orientation == Configuration.ORIENTATION_PORTRAIT - && !isInMultiWindowMode(); + // Do not enable until we have confirmed more apps are using sharing shortcuts + // Check git history for enablement logic + return false; } public ChooserListAdapter getListAdapter() { diff --git a/core/java/com/android/internal/app/ChooserGridLayoutManager.java b/core/java/com/android/internal/app/ChooserGridLayoutManager.java index 317a987cf3599cc7c15bc802029019ee0ec9273e..c50ebd9562c99780c619f182d9af59f0f548aed0 100644 --- a/core/java/com/android/internal/app/ChooserGridLayoutManager.java +++ b/core/java/com/android/internal/app/ChooserGridLayoutManager.java @@ -28,6 +28,8 @@ import com.android.internal.widget.RecyclerView; */ public class ChooserGridLayoutManager extends GridLayoutManager { + private boolean mVerticalScrollEnabled = true; + /** * Constructor used when layout manager is set in XML by RecyclerView attribute * "layoutManager". If spanCount is not specified in the XML, it defaults to a @@ -67,4 +69,13 @@ public class ChooserGridLayoutManager extends GridLayoutManager { // Do not count the footer view in the official count return super.getRowCountForAccessibility(recycler, state) - 1; } + + void setVerticalScrollEnabled(boolean verticalScrollEnabled) { + mVerticalScrollEnabled = verticalScrollEnabled; + } + + @Override + public boolean canScrollVertically() { + return mVerticalScrollEnabled && super.canScrollVertically(); + } } diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index f4fb993fbb9322dee12e9e18e2fce1c5ed1a784b..00b5cb646bca2b9199b792ddca392b406a9b67e4 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -95,6 +95,7 @@ public class ChooserListAdapter extends ResolverListAdapter { mSelectableTargetInfoCommunicator; private int mNumShortcutResults = 0; + private Map mIconLoaders = new HashMap<>(); // Reserve spots for incoming direct share targets by adding placeholders private ChooserTargetInfo @@ -181,6 +182,7 @@ public class ChooserListAdapter extends ResolverListAdapter { ri.icon = 0; } mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii, makePresentationGetter(ri))); + if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break; } } } @@ -238,11 +240,42 @@ public class ChooserListAdapter extends ResolverListAdapter { @Override protected void onBindView(View view, TargetInfo info, int position) { - super.onBindView(view, info, position); - if (info == null) return; + final ViewHolder holder = (ViewHolder) view.getTag(); + if (info == null) { + holder.icon.setImageDrawable( + mContext.getDrawable(R.drawable.resolver_icon_placeholder)); + return; + } + + if (!(info instanceof DisplayResolveInfo)) { + holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel()); + holder.bindIcon(info); + + if (info instanceof SelectableTargetInfo) { + // direct share targets should append the application name for a better readout + DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo(); + CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : ""; + CharSequence extendedInfo = info.getExtendedInfo(); + String contentDescription = String.join(" ", info.getDisplayLabel(), + extendedInfo != null ? extendedInfo : "", appName); + holder.updateContentDescription(contentDescription); + } + } else { + DisplayResolveInfo dri = (DisplayResolveInfo) info; + holder.bindLabel(dri.getDisplayLabel(), dri.getExtendedInfo(), alwaysShowSubLabel()); + LoadIconTask task = mIconLoaders.get(dri); + if (task == null) { + task = new LoadIconTask(dri, holder); + mIconLoaders.put(dri, task); + task.execute(); + } else { + // The holder was potentially changed as the underlying items were + // reshuffled, so reset the target holder + task.setViewHolder(holder); + } + } // If target is loading, show a special placeholder shape in the label, make unclickable - final ViewHolder holder = (ViewHolder) view.getTag(); if (info instanceof ChooserActivity.PlaceHolderTargetInfo) { final int maxWidth = mContext.getResources().getDimensionPixelSize( R.dimen.chooser_direct_share_label_placeholder_max_width); @@ -274,33 +307,43 @@ public class ChooserListAdapter extends ResolverListAdapter { } void updateAlphabeticalList() { - mSortedList.clear(); - List tempList = new ArrayList<>(); - tempList.addAll(mDisplayList); - tempList.addAll(mCallerTargets); - if (mEnableStackedApps) { - // Consolidate multiple targets from same app. - Map consolidated = new HashMap<>(); - for (DisplayResolveInfo info : tempList) { - String packageName = info.getResolvedComponentName().getPackageName(); - DisplayResolveInfo multiDri = consolidated.get(packageName); - if (multiDri == null) { - consolidated.put(packageName, info); - } else if (multiDri instanceof MultiDisplayResolveInfo) { - ((MultiDisplayResolveInfo) multiDri).addTarget(info); - } else { - // create consolidated target from the single DisplayResolveInfo - MultiDisplayResolveInfo multiDisplayResolveInfo = + new AsyncTask>() { + @Override + protected List doInBackground(Void... voids) { + List allTargets = new ArrayList<>(); + allTargets.addAll(mDisplayList); + allTargets.addAll(mCallerTargets); + if (!mEnableStackedApps) { + return allTargets; + } + // Consolidate multiple targets from same app. + Map consolidated = new HashMap<>(); + for (DisplayResolveInfo info : allTargets) { + String packageName = info.getResolvedComponentName().getPackageName(); + DisplayResolveInfo multiDri = consolidated.get(packageName); + if (multiDri == null) { + consolidated.put(packageName, info); + } else if (multiDri instanceof MultiDisplayResolveInfo) { + ((MultiDisplayResolveInfo) multiDri).addTarget(info); + } else { + // create consolidated target from the single DisplayResolveInfo + MultiDisplayResolveInfo multiDisplayResolveInfo = new MultiDisplayResolveInfo(packageName, multiDri); - multiDisplayResolveInfo.addTarget(info); - consolidated.put(packageName, multiDisplayResolveInfo); + multiDisplayResolveInfo.addTarget(info); + consolidated.put(packageName, multiDisplayResolveInfo); + } } + List groupedTargets = new ArrayList<>(); + groupedTargets.addAll(consolidated.values()); + Collections.sort(groupedTargets, new ChooserActivity.AzInfoComparator(mContext)); + return groupedTargets; } - mSortedList.addAll(consolidated.values()); - } else { - mSortedList.addAll(tempList); - } - Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext)); + @Override + protected void onPostExecute(List newList) { + mSortedList = newList; + notifyDataSetChanged(); + } + }.execute(); } @Override @@ -320,7 +363,7 @@ public class ChooserListAdapter extends ResolverListAdapter { public int getCallerTargetCount() { - return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS); + return mCallerTargets.size(); } /** @@ -346,8 +389,9 @@ public class ChooserListAdapter extends ResolverListAdapter { } int getAlphaTargetCount() { - int standardCount = mSortedList.size(); - return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0; + int groupedCount = mSortedList.size(); + int ungroupedCount = mCallerTargets.size() + mDisplayList.size(); + return ungroupedCount > mChooserListCommunicator.getMaxRankedTargets() ? groupedCount : 0; } /** @@ -549,7 +593,7 @@ public class ChooserListAdapter extends ResolverListAdapter { mChooserTargetScores.put(componentName, new HashMap<>()); } mChooserTargetScores.get(componentName).put(shortcutInfo.getShortLabel().toString(), - shortcutInfo.getRank()); + target.getRank()); } mChooserTargetScores.keySet().forEach(key -> rankTargetsWithinComponent(key)); } @@ -826,6 +870,12 @@ public class ChooserListAdapter extends ResolverListAdapter { return mServiceTargets.get(value).getChooserTarget(); } + protected boolean alwaysShowSubLabel() { + // Always show a subLabel for visual consistency across list items. Show an empty + // subLabel if the subLabel is the same as the label + return true; + } + /** * Rather than fully sorting the input list, this sorting task will put the top k elements * in the head of input list and fill the tail with other elements in undetermined order. @@ -867,6 +917,7 @@ public class ChooserListAdapter extends ResolverListAdapter { if (getAppPredictor() != null) { getAppPredictor().unregisterPredictionUpdates(mAppPredictorCallback); getAppPredictor().destroy(); + setAppPredictor(null); } } diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index 774be3c9c4b8832241aa216154b1ec0d1274b1e3..ffa6041721c6f5e34479ca674da65d6f2b24e2e5 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -38,6 +38,7 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd private final ChooserProfileDescriptor[] mItems; private final boolean mIsSendAction; + private int mBottomOffset; ChooserMultiProfilePagerAdapter(Context context, ChooserActivity.ChooserGridAdapter adapter, @@ -245,6 +246,16 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd } } + void setEmptyStateBottomOffset(int bottomOffset) { + mBottomOffset = bottomOffset; + } + + @Override + protected void setupContainerPadding(View container) { + container.setPadding(container.getPaddingLeft(), container.getPaddingTop(), + container.getPaddingRight(), container.getPaddingBottom() + mBottomOffset); + } + class ChooserProfileDescriptor extends ProfileDescriptor { private ChooserActivity.ChooserGridAdapter chooserGridAdapter; private RecyclerView recyclerView; diff --git a/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java index 3991a7674f38b7fce82e14d2fb2e1d5a53205da4..21063d5d5857b1e3ffbc02b5cbf8b4c01f618144 100644 --- a/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java +++ b/core/java/com/android/internal/app/ChooserTargetActionsDialogFragment.java @@ -26,7 +26,6 @@ import static java.util.stream.Collectors.toList; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; -import android.app.AlertDialog.Builder; import android.app.Dialog; import android.app.DialogFragment; import android.content.ComponentName; @@ -34,21 +33,25 @@ import android.content.DialogInterface; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; import android.util.Pair; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import com.android.internal.R; import com.android.internal.app.chooser.DisplayResolveInfo; +import com.android.internal.widget.RecyclerView; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * Shows a dialog with actions to take on a chooser target. @@ -68,47 +71,86 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment mTargetInfos = targets; } - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { + /** + * Recreate the layout from scratch to match new Sharesheet redlines + */ + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + Bundle savedInstanceState) { + + // Make the background transparent to show dialog rounding + Optional.of(getDialog()).map(Dialog::getWindow) + .ifPresent(window -> { + window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + }); // Fetch UI details from target info - List> items = mTargetInfos.stream().map(dri -> { - return new Pair<>(getItemLabel(dri), getItemIcon(dri)); + List> items = mTargetInfos.stream().map(dri -> { + return new Pair<>(getItemIcon(dri), getItemLabel(dri)); }).collect(toList()); + View v = inflater.inflate(R.layout.chooser_dialog, container, false); + + TextView title = v.findViewById(R.id.title); + ImageView icon = v.findViewById(R.id.icon); + RecyclerView rv = v.findViewById(R.id.listContainer); + final ResolveInfoPresentationGetter pg = getProvidingAppPresentationGetter(); - return new Builder(getContext()) - .setTitle(pg.getLabel()) - .setIcon(pg.getIcon(mUserHandle)) - .setCancelable(true) - .setAdapter(getAdapterForContent(items), this) - .create(); + title.setText(pg.getLabel()); + icon.setImageDrawable(pg.getIcon(mUserHandle)); + rv.setAdapter(new VHAdapter(items)); + + return v; + } + + class VHAdapter extends RecyclerView.Adapter { + + List> mItems; + + VHAdapter(List> items) { + mItems = items; + } + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH(LayoutInflater.from(parent.getContext()).inflate( + R.layout.chooser_dialog_item, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + holder.bind(mItems.get(position), position); + } + + @Override + public int getItemCount() { + return mItems.size(); + } } - protected ArrayAdapter> getAdapterForContent( - List> items) { - return new ArrayAdapter>(getContext(), - R.layout.chooser_dialog_item, R.id.text, items) { - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View v = super.getView(position, convertView, parent); // super recycles views - TextView label = v.findViewById(R.id.text); - ImageView icon = v.findViewById(R.id.icon); - - Pair pair = getItem(position); - label.setText(pair.first); - - // Hide icon view if one isn't available - if (pair.second == null) { - icon.setVisibility(View.GONE); - } else { - icon.setImageDrawable(pair.second); - icon.setVisibility(View.VISIBLE); - } - - return v; + class VH extends RecyclerView.ViewHolder { + TextView mLabel; + ImageView mIcon; + + VH(@NonNull View itemView) { + super(itemView); + mLabel = itemView.findViewById(R.id.text); + mIcon = itemView.findViewById(R.id.icon); + } + + public void bind(Pair item, int position) { + mLabel.setText(item.second); + + if (item.first == null) { + mIcon.setVisibility(View.GONE); + } else { + mIcon.setVisibility(View.VISIBLE); + mIcon.setImageDrawable(item.first); } - }; + + itemView.setOnClickListener(v -> onClick(getDialog(), position)); + } } @Override diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index 06c21ab8832d7d0082d80db2e2c03dfc928da32d..51e56b7fca43ca10d64bc939906a368581bd76db 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -36,10 +36,10 @@ interface IAppOpsService { // and not be reordered int checkOperation(int code, int uid, String packageName); int noteOperation(int code, int uid, String packageName, @nullable String attributionTag, - boolean shouldCollectAsyncNotedOp, String message); + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage); int startOperation(IBinder clientId, int code, int uid, String packageName, @nullable String attributionTag, boolean startIfModeDefault, - boolean shouldCollectAsyncNotedOp, String message); + boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage); @UnsupportedAppUsage void finishOperation(IBinder clientId, int code, int uid, String packageName, @nullable String attributionTag); @@ -54,7 +54,8 @@ interface IAppOpsService { int noteProxyOperation(int code, int proxiedUid, String proxiedPackageName, String proxiedAttributionTag, int proxyUid, String proxyPackageName, - String proxyAttributionTag, boolean shouldCollectAsyncNotedOp, String message); + String proxyAttributionTag, boolean shouldCollectAsyncNotedOp, String message, + boolean shouldCollectMessage); // Remaining methods are only used in Java. int checkPackage(int uid, String packageName); @@ -76,6 +77,7 @@ interface IAppOpsService { void addHistoricalOps(in AppOpsManager.HistoricalOps ops); void resetHistoryParameters(); void clearHistory(); + void rebootHistory(long offlineDurationMillis); List getUidOps(int uid, in int[] ops); void setUidMode(int code, int uid, int mode); @UnsupportedAppUsage diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 71ee8af8b11a97285cbcb3d2aeb1244ef02c1be8..15ba8e8c11f78bb06b93139552002a5d55746d80 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -265,4 +265,16 @@ interface IVoiceInteractionManagerService { void performDirectAction(in IBinder token, String actionId, in Bundle arguments, int taskId, IBinder assistToken, in RemoteCallback cancellationCallback, in RemoteCallback resultCallback); + + /** + * Temporarily disables voice interaction (for example, on Automotive when the display is off). + * + * It will shutdown the service, and only re-enable it after it's called again (or after a + * system restart). + * + * NOTE: it's only effective when the service itself is available / enabled in the device, so + * calling setDisable(false) would be a no-op when it isn't. + */ + void setDisabled(boolean disabled); + } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index e65d1fe9ce53720d0e31c52d71f7eec26265d924..61a52bcc03f9166ee9f157768517ab471a421696 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -18,6 +18,7 @@ package com.android.internal.app; import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; +import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER; import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE; import android.annotation.Nullable; @@ -246,6 +247,7 @@ public class IntentForwarderActivity extends Activity { int selectedProfile = findSelectedProfile(className); sanitizeIntent(intentReceived); intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile); + intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId)); startActivityAsCaller(intentReceived, null, null, false, userId); finish(); } diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java index 157e0a74712b846ad3ff88af3ad16b3db70f3b4d..986bbc8628ec16d31080f86897aac065a1c6e382 100644 --- a/core/java/com/android/internal/app/PlatLogoActivity.java +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,29 +17,32 @@ package com.android.internal.app; import android.animation.ObjectAnimator; -import android.animation.TimeAnimator; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActionBar; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; -import android.content.res.ColorStateList; -import android.graphics.Bitmap; -import android.graphics.BitmapShader; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.ColorFilter; -import android.graphics.Matrix; +import android.graphics.LinearGradient; import android.graphics.Paint; -import android.graphics.Path; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.provider.Settings; +import android.util.AttributeSet; import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; +import android.view.animation.PathInterpolator; +import android.widget.FrameLayout; import android.widget.ImageView; import com.android.internal.R; @@ -50,23 +53,16 @@ import org.json.JSONObject; * @hide */ public class PlatLogoActivity extends Activity { - ImageView mZeroView, mOneView; - BackslashDrawable mBackslash; - int mClicks; - - static final Paint sPaint = new Paint(); - static { - sPaint.setStyle(Paint.Style.STROKE); - sPaint.setStrokeWidth(4f); - sPaint.setStrokeCap(Paint.Cap.SQUARE); - } + private static final boolean WRITE_SETTINGS = true; + + private static final String R_EGG_UNLOCK_SETTING = "egg_mode_r"; + + private static final int UNLOCK_TRIES = 3; + + BigDialView mDialView; @Override protected void onPause() { - if (mBackslash != null) { - mBackslash.stopAnimating(); - } - mClicks = 0; super.onPause(); } @@ -80,114 +76,46 @@ public class PlatLogoActivity extends Activity { getWindow().setNavigationBarColor(0); getWindow().setStatusBarColor(0); - getActionBar().hide(); - - setContentView(R.layout.platlogo_layout); - - mBackslash = new BackslashDrawable((int) (50 * dp)); - - mOneView = findViewById(R.id.one); - mOneView.setImageDrawable(new OneDrawable()); - mZeroView = findViewById(R.id.zero); - mZeroView.setImageDrawable(new ZeroDrawable()); - - final ViewGroup root = (ViewGroup) mOneView.getParent(); - root.setClipChildren(false); - root.setBackground(mBackslash); - root.getBackground().setAlpha(0x20); - - View.OnTouchListener tl = new View.OnTouchListener() { - float mOffsetX, mOffsetY; - long mClickTime; - ObjectAnimator mRotAnim; - @Override - public boolean onTouch(View v, MotionEvent event) { - measureTouchPressure(event); - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - v.animate().scaleX(1.1f).scaleY(1.1f); - v.getParent().bringChildToFront(v); - mOffsetX = event.getRawX() - v.getX(); - mOffsetY = event.getRawY() - v.getY(); - long now = System.currentTimeMillis(); - if (now - mClickTime < 350) { - mRotAnim = ObjectAnimator.ofFloat(v, View.ROTATION, - v.getRotation(), v.getRotation() + 3600); - mRotAnim.setDuration(10000); - mRotAnim.start(); - mClickTime = 0; - } else { - mClickTime = now; - } - break; - case MotionEvent.ACTION_MOVE: - v.setX(event.getRawX() - mOffsetX); - v.setY(event.getRawY() - mOffsetY); - v.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE); - break; - case MotionEvent.ACTION_UP: - v.performClick(); - // fall through - case MotionEvent.ACTION_CANCEL: - v.animate().scaleX(1f).scaleY(1f); - if (mRotAnim != null) mRotAnim.cancel(); - testOverlap(); - break; - } - return true; - } - }; - - findViewById(R.id.one).setOnTouchListener(tl); - findViewById(R.id.zero).setOnTouchListener(tl); - findViewById(R.id.text).setOnTouchListener(tl); - } + final ActionBar ab = getActionBar(); + if (ab != null) ab.hide(); - private void testOverlap() { - final float width = mZeroView.getWidth(); - final float targetX = mZeroView.getX() + width * .2f; - final float targetY = mZeroView.getY() + width * .3f; - if (Math.hypot(targetX - mOneView.getX(), targetY - mOneView.getY()) < width * .2f - && Math.abs(mOneView.getRotation() % 360 - 315) < 15) { - mOneView.animate().x(mZeroView.getX() + width * .2f); - mOneView.animate().y(mZeroView.getY() + width * .3f); - mOneView.setRotation(mOneView.getRotation() % 360); - mOneView.animate().rotation(315); - mOneView.performHapticFeedback(HapticFeedbackConstants.CONFIRM); - - mBackslash.startAnimating(); - - mClicks++; - if (mClicks >= 7) { - launchNextStage(); - } + mDialView = new BigDialView(this, null); + if (Settings.System.getLong(getContentResolver(), + R_EGG_UNLOCK_SETTING, 0) == 0) { + mDialView.setUnlockTries(UNLOCK_TRIES); } else { - mBackslash.stopAnimating(); + mDialView.setUnlockTries(0); } + + final FrameLayout layout = new FrameLayout(this); + layout.setBackgroundColor(0xFFFF0000); + layout.addView(mDialView, FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT); + setContentView(layout); } - private void launchNextStage() { + private void launchNextStage(boolean locked) { final ContentResolver cr = getContentResolver(); - if (Settings.System.getLong(cr, "egg_mode" /* Settings.System.EGG_MODE */, 0) == 0) { - // For posterity: the moment this user unlocked the easter egg - try { + try { + if (WRITE_SETTINGS) { Settings.System.putLong(cr, - "egg_mode", // Settings.System.EGG_MODE, - System.currentTimeMillis()); - } catch (RuntimeException e) { - Log.e("com.android.internal.app.PlatLogoActivity", "Can't write settings", e); + R_EGG_UNLOCK_SETTING, + locked ? 0 : System.currentTimeMillis()); } + } catch (RuntimeException e) { + Log.e("com.android.internal.app.PlatLogoActivity", "Can't write settings", e); } + try { startActivity(new Intent(Intent.ACTION_MAIN) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK) + | Intent.FLAG_ACTIVITY_CLEAR_TASK) .addCategory("com.android.internal.category.PLATLOGO")); } catch (ActivityNotFoundException ex) { Log.e("com.android.internal.app.PlatLogoActivity", "No more eggs."); } - finish(); + //finish(); // no longer finish upon unlock; it's fun to frob the dial } static final String TOUCH_STATS = "touch.stats"; @@ -223,7 +151,10 @@ public class PlatLogoActivity extends Activity { if (mPressureMax >= 0) { touchData.put("min", mPressureMin); touchData.put("max", mPressureMax); - Settings.System.putString(getContentResolver(), TOUCH_STATS, touchData.toString()); + if (WRITE_SETTINGS) { + Settings.System.putString(getContentResolver(), TOUCH_STATS, + touchData.toString()); + } } } catch (Exception e) { Log.e("com.android.internal.app.PlatLogoActivity", "Can't write touch settings", e); @@ -242,149 +173,274 @@ public class PlatLogoActivity extends Activity { super.onStop(); } - static class ZeroDrawable extends Drawable { - int mTintColor; + class BigDialView extends ImageView { + private static final int COLOR_GREEN = 0xff3ddc84; + private static final int COLOR_BLUE = 0xff4285f4; + private static final int COLOR_NAVY = 0xff073042; + private static final int COLOR_ORANGE = 0xfff86734; + private static final int COLOR_CHARTREUSE = 0xffeff7cf; + private static final int COLOR_LIGHTBLUE = 0xffd7effe; - @Override - public void draw(Canvas canvas) { - sPaint.setColor(mTintColor | 0xFF000000); + private static final int STEPS = 11; + private static final float VALUE_CHANGE_MAX = 1f / STEPS; - canvas.save(); - canvas.scale(canvas.getWidth() / 24f, canvas.getHeight() / 24f); + private BigDialDrawable mDialDrawable; + private boolean mWasLocked; - canvas.drawCircle(12f, 12f, 10f, sPaint); - canvas.restore(); + BigDialView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); } - @Override - public void setAlpha(int alpha) { } - - @Override - public void setColorFilter(ColorFilter colorFilter) { } - - @Override - public void setTintList(ColorStateList tint) { - mTintColor = tint.getDefaultColor(); + BigDialView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); } - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; + BigDialView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); } - } - static class OneDrawable extends Drawable { - int mTintColor; + private void init() { + mDialDrawable = new BigDialDrawable(); + setImageDrawable(mDialDrawable); + } @Override - public void draw(Canvas canvas) { - sPaint.setColor(mTintColor | 0xFF000000); - - canvas.save(); - canvas.scale(canvas.getWidth() / 24f, canvas.getHeight() / 24f); - - final Path p = new Path(); - p.moveTo(12f, 21.83f); - p.rLineTo(0f, -19.67f); - p.rLineTo(-5f, 0f); - canvas.drawPath(p, sPaint); - canvas.restore(); + public void onDraw(Canvas c) { + super.onDraw(c); } - @Override - public void setAlpha(int alpha) { } + double toPositiveDegrees(double rad) { + return (Math.toDegrees(rad) + 360 - 90) % 360; + } @Override - public void setColorFilter(ColorFilter colorFilter) { } + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mWasLocked = mDialDrawable.isLocked(); + // pass through + case MotionEvent.ACTION_MOVE: + float x = ev.getX(); + float y = ev.getY(); + float cx = (getLeft() + getRight()) / 2f; + float cy = (getTop() + getBottom()) / 2f; + float angle = (float) toPositiveDegrees(Math.atan2(x - cx, y - cy)); + final int oldLevel = mDialDrawable.getUserLevel(); + mDialDrawable.touchAngle(angle); + final int newLevel = mDialDrawable.getUserLevel(); + if (oldLevel != newLevel) { + performHapticFeedback(newLevel == STEPS + ? HapticFeedbackConstants.CONFIRM + : HapticFeedbackConstants.CLOCK_TICK); + } + return true; + case MotionEvent.ACTION_UP: + if (mWasLocked != mDialDrawable.isLocked()) { + launchNextStage(mDialDrawable.isLocked()); + } + return true; + } + return false; + } @Override - public void setTintList(ColorStateList tint) { - mTintColor = tint.getDefaultColor(); + public boolean performClick() { + if (mDialDrawable.getUserLevel() < STEPS - 1) { + mDialDrawable.setUserLevel(mDialDrawable.getUserLevel() + 1); + performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); + } + return true; } - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; + void setUnlockTries(int tries) { + mDialDrawable.setUnlockTries(tries); } - } - private static class BackslashDrawable extends Drawable implements TimeAnimator.TimeListener { - Bitmap mTile; - Paint mPaint = new Paint(); - BitmapShader mShader; - TimeAnimator mAnimator = new TimeAnimator(); - Matrix mMatrix = new Matrix(); + private class BigDialDrawable extends Drawable { + public final int STEPS = 10; + private int mUnlockTries = 0; + final Paint mPaint = new Paint(); + final Drawable mEleven; + private boolean mNightMode; + private float mValue = 0f; + float mElevenAnim = 0f; + ObjectAnimator mElevenShowAnimator = ObjectAnimator.ofFloat(this, "elevenAnim", 0f, + 1f).setDuration(300); + ObjectAnimator mElevenHideAnimator = ObjectAnimator.ofFloat(this, "elevenAnim", 1f, + 0f).setDuration(500); + + BigDialDrawable() { + mNightMode = getContext().getResources().getConfiguration().isNightModeActive(); + mEleven = getContext().getDrawable(R.drawable.ic_number11); + mElevenShowAnimator.setInterpolator(new PathInterpolator(0.4f, 0f, 0.2f, 1f)); + mElevenHideAnimator.setInterpolator(new PathInterpolator(0.8f, 0.2f, 0.6f, 1f)); + } - public void draw(Canvas canvas) { - canvas.drawPaint(mPaint); - } + public void setUnlockTries(int count) { + if (mUnlockTries != count) { + mUnlockTries = count; + setValue(getValue()); + invalidateSelf(); + } + } - BackslashDrawable(int width) { - mTile = Bitmap.createBitmap(width, width, Bitmap.Config.ALPHA_8); - mAnimator.setTimeListener(this); - - final Canvas tileCanvas = new Canvas(mTile); - final float w = tileCanvas.getWidth(); - final float h = tileCanvas.getHeight(); - - final Path path = new Path(); - path.moveTo(0, 0); - path.lineTo(w / 2, 0); - path.lineTo(w, h / 2); - path.lineTo(w, h); - path.close(); - - path.moveTo(0, h / 2); - path.lineTo(w / 2, h); - path.lineTo(0, h); - path.close(); - - final Paint slashPaint = new Paint(); - slashPaint.setAntiAlias(true); - slashPaint.setStyle(Paint.Style.FILL); - slashPaint.setColor(0xFF000000); - tileCanvas.drawPath(path, slashPaint); - - //mPaint.setColor(0xFF0000FF); - mShader = new BitmapShader(mTile, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); - mPaint.setShader(mShader); - } + boolean isLocked() { + return mUnlockTries > 0; + } - public void startAnimating() { - if (!mAnimator.isStarted()) { - mAnimator.start(); + public void setValue(float v) { + // until the dial is "unlocked", you can't turn it all the way to 11 + final float max = isLocked() ? 1f - 1f / STEPS : 1f; + mValue = v < 0f ? 0f : v > max ? max : v; + invalidateSelf(); } - } - public void stopAnimating() { - if (mAnimator.isStarted()) { - mAnimator.cancel(); + public float getValue() { + return mValue; } - } - @Override - public void setAlpha(int alpha) { - mPaint.setAlpha(alpha); - } + public int getUserLevel() { + return Math.round(getValue() * STEPS - 0.25f); + } - @Override - public void setColorFilter(ColorFilter colorFilter) { - mPaint.setColorFilter(colorFilter); - } + public void setUserLevel(int i) { + setValue(getValue() + ((float) i) / STEPS); + } - @Override - public int getOpacity() { - return PixelFormat.TRANSLUCENT; - } + public float getElevenAnim() { + return mElevenAnim; + } - @Override - public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { - if (mShader != null) { - mMatrix.postTranslate(deltaTime / 4f, 0); - mShader.setLocalMatrix(mMatrix); - invalidateSelf(); + public void setElevenAnim(float f) { + if (mElevenAnim != f) { + mElevenAnim = f; + invalidateSelf(); + } + } + + @Override + public void draw(@NonNull Canvas canvas) { + final Rect bounds = getBounds(); + final int w = bounds.width(); + final int h = bounds.height(); + final float w2 = w / 2f; + final float h2 = h / 2f; + final float radius = w / 4f; + + canvas.drawColor(mNightMode ? COLOR_NAVY : COLOR_LIGHTBLUE); + + canvas.save(); + canvas.rotate(45, w2, h2); + canvas.clipRect(w2, h2 - radius, Math.min(w, h), h2 + radius); + final int gradientColor = mNightMode ? 0x60000020 : (0x10FFFFFF & COLOR_NAVY); + mPaint.setShader( + new LinearGradient(w2, h2, Math.min(w, h), h2, gradientColor, + 0x00FFFFFF & gradientColor, Shader.TileMode.CLAMP)); + mPaint.setColor(Color.BLACK); + canvas.drawPaint(mPaint); + mPaint.setShader(null); + canvas.restore(); + + mPaint.setStyle(Paint.Style.FILL); + mPaint.setColor(COLOR_GREEN); + + canvas.drawCircle(w2, h2, radius, mPaint); + + mPaint.setColor(mNightMode ? COLOR_LIGHTBLUE : COLOR_NAVY); + final float cx = w * 0.85f; + for (int i = 0; i < STEPS; i++) { + final float f = (float) i / STEPS; + canvas.save(); + final float angle = valueToAngle(f); + canvas.rotate(-angle, w2, h2); + canvas.drawCircle(cx, h2, (i <= getUserLevel()) ? 20 : 5, mPaint); + canvas.restore(); + } + + if (mElevenAnim > 0f) { + final int color = COLOR_ORANGE; + final int size2 = (int) ((0.5 + 0.5f * mElevenAnim) * w / 14); + final float cx11 = cx + size2 / 4f; + mEleven.setBounds((int) cx11 - size2, (int) h2 - size2, + (int) cx11 + size2, (int) h2 + size2); + final int alpha = 0xFFFFFF | ((int) clamp(0xFF * 2 * mElevenAnim, 0, 0xFF) + << 24); + mEleven.setTint(alpha & color); + mEleven.draw(canvas); + } + + // don't want to use the rounded value here since the quantization will be visible + final float angle = valueToAngle(mValue); + + // it's easier to draw at far-right and rotate backwards + canvas.rotate(-angle, w2, h2); + mPaint.setColor(Color.WHITE); + final float dimple = w2 / 12f; + canvas.drawCircle(w - radius - dimple * 2, h2, dimple, mPaint); + } + + float clamp(float x, float a, float b) { + return x < a ? a : x > b ? b : x; + } + + float angleToValue(float a) { + return 1f - clamp(a / (360 - 45), 0f, 1f); + } + + // rotation: min is at 4:30, max is at 3:00 + float valueToAngle(float v) { + return (1f - v) * (360 - 45); + } + + public void touchAngle(float a) { + final int oldUserLevel = getUserLevel(); + final float newValue = angleToValue(a); + // this is how we prevent the knob from snapping from max back to min, or from + // jumping around wherever the user presses. The new value must be pretty close + // to the + // previous one. + if (Math.abs(newValue - getValue()) < VALUE_CHANGE_MAX) { + setValue(newValue); + + if (isLocked() && oldUserLevel != STEPS - 1 && getUserLevel() == STEPS - 1) { + mUnlockTries--; + } else if (!isLocked() && getUserLevel() == 0) { + mUnlockTries = UNLOCK_TRIES; + } + + if (!isLocked()) { + if (getUserLevel() == STEPS && mElevenAnim != 1f + && !mElevenShowAnimator.isRunning()) { + mElevenHideAnimator.cancel(); + mElevenShowAnimator.start(); + } else if (getUserLevel() != STEPS && mElevenAnim == 1f + && !mElevenHideAnimator.isRunning()) { + mElevenShowAnimator.cancel(); + mElevenHideAnimator.start(); + } + } + } + } + + @Override + public void setAlpha(int i) { + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; } } } } + + diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index b82f0dfcbe12dc27f94e8579d1601c483dad98d8..233231cfcfdf0c3ceb82a1c656243d7f09012055 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -20,9 +20,6 @@ import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.PermissionChecker.PID_UNKNOWN; -import static com.android.internal.app.AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL; -import static com.android.internal.app.AbstractMultiProfilePagerAdapter.PROFILE_WORK; - import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.UiThread; @@ -159,9 +156,9 @@ public class ResolverActivity extends Activity implements protected static final String METRICS_CATEGORY_RESOLVER = "intent_resolver"; protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser"; - /** - * TODO(arangelov): Remove a couple of weeks after work/personal tabs are finalized. - */ + /** Tracks if we should ignore future broadcasts telling us the work profile is enabled */ + private boolean mWorkProfileHasBeenEnabled = false; + @VisibleForTesting public static boolean ENABLE_TABBED_VIEW = true; private static final String TAB_TAG_PERSONAL = "personal"; @@ -184,6 +181,18 @@ public class ResolverActivity extends Activity implements static final String EXTRA_SELECTED_PROFILE = "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE"; + /** + * {@link UserHandle} extra to indicate the user of the user that the starting intent + * originated from. + *

This is not necessarily the same as {@link #getUserId()} or {@link UserHandle#myUserId()}, + * as there are edge cases when the intent resolver is launched in the other profile. + * For example, when we have 0 resolved apps in current profile and multiple resolved + * apps in the other profile, opening a link from the current profile launches the intent + * resolver in the other one. b/148536209 for more info. + */ + static final String EXTRA_CALLING_USER = + "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER"; + static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL; static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK; @@ -470,17 +479,20 @@ public class ResolverActivity extends Activity implements // the intent resolver is started in the other profile. Since this is the only case when // this happens, we check for it here and set the current profile's tab. int selectedProfile = getCurrentProfile(); - UserHandle intentUser = UserHandle.of(getLaunchingUserId()); + UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER) + ? getIntent().getParcelableExtra(EXTRA_CALLING_USER) + : getUser(); if (!getUser().equals(intentUser)) { if (getPersonalProfileUserHandle().equals(intentUser)) { selectedProfile = PROFILE_PERSONAL; } else if (getWorkProfileUserHandle().equals(intentUser)) { selectedProfile = PROFILE_WORK; } - } - int selectedProfileExtra = getSelectedProfileExtra(); - if (selectedProfileExtra != -1) { - selectedProfile = selectedProfileExtra; + } else { + int selectedProfileExtra = getSelectedProfileExtra(); + if (selectedProfileExtra != -1) { + selectedProfile = selectedProfileExtra; + } } // We only show the default app for the profile of the current user. The filterLastUsed // flag determines whether to show a default app and that app is not shown in the @@ -535,22 +547,6 @@ public class ResolverActivity extends Activity implements return selectedProfile; } - /** - * Returns the user id of the user that the starting intent originated from. - *

This is not necessarily equal to {@link #getUserId()} or {@link UserHandle#myUserId()}, - * as there are edge cases when the intent resolver is launched in the other profile. - * For example, when we have 0 resolved apps in current profile and multiple resolved apps - * in the other profile, opening a link from the current profile launches the intent resolver - * in the other one. b/148536209 for more info. - */ - private int getLaunchingUserId() { - int contentUserHint = getIntent().getContentUserHint(); - if (contentUserHint == UserHandle.USER_CURRENT) { - return UserHandle.myUserId(); - } - return contentUserHint; - } - protected @Profile int getCurrentProfile() { return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK); } @@ -829,12 +825,23 @@ public class ResolverActivity extends Activity implements if (shouldShowTabs()) { mWorkProfileStateReceiver = createWorkProfileStateReceiver(); registerWorkProfileStateReceiver(); + + mWorkProfileHasBeenEnabled = isWorkProfileEnabled(); } } + private boolean isWorkProfileEnabled() { + UserHandle workUserHandle = getWorkProfileUserHandle(); + UserManager userManager = getSystemService(UserManager.class); + + return !userManager.isQuietModeEnabled(workUserHandle) + && userManager.isUserUnlocked(workUserHandle); + } + private void registerWorkProfileStateReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_UNLOCKED); + filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); registerReceiverAsUser(mWorkProfileStateReceiver, UserHandle.ALL, filter, null, null); } @@ -1235,8 +1242,8 @@ public class ResolverActivity extends Activity implements } if (target != null) { - if (intent != null) { - intent.fixUris(UserHandle.myUserId()); + if (intent != null && isLaunchingTargetInOtherProfile()) { + prepareIntentForCrossProfileLaunch(intent); } safelyStartActivity(target); @@ -1250,6 +1257,15 @@ public class ResolverActivity extends Activity implements return true; } + private void prepareIntentForCrossProfileLaunch(Intent intent) { + intent.fixUris(UserHandle.myUserId()); + } + + private boolean isLaunchingTargetInOtherProfile() { + return mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() + != UserHandle.myUserId(); + } + @VisibleForTesting public void safelyStartActivity(TargetInfo cti) { // We're dispatching intents that might be coming from legacy apps, so @@ -1263,13 +1279,17 @@ public class ResolverActivity extends Activity implements } private void safelyStartActivityInternal(TargetInfo cti) { - if (mPersonalPackageMonitor != null) { - mPersonalPackageMonitor.unregister(); - } - if (mWorkPackageMonitor != null) { - mWorkPackageMonitor.unregister(); + // If the target is suspended, the activity will not be successfully launched. + // Do not unregister from package manager updates in this case + if (!cti.isSuspended()) { + if (mPersonalPackageMonitor != null) { + mPersonalPackageMonitor.unregister(); + } + if (mWorkPackageMonitor != null) { + mWorkPackageMonitor.unregister(); + } + mRegistered = false; } - mRegistered = false; // If needed, show that intent is forwarded // from managed profile to owner or other way around. if (mProfileSwitchMessageId != -1) { @@ -1646,10 +1666,18 @@ public class ResolverActivity extends Activity implements viewPager.setVisibility(View.VISIBLE); tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage()); mMultiProfilePagerAdapter.setOnProfileSelectedListener( - index -> { - tabHost.setCurrentTab(index); - resetButtonBar(); - resetCheckedItem(); + new AbstractMultiProfilePagerAdapter.OnProfileSelectedListener() { + @Override + public void onProfileSelected(int index) { + tabHost.setCurrentTab(index); + resetButtonBar(); + resetCheckedItem(); + } + + @Override + public void onProfilePageStateChanged(int state) { + onHorizontalSwipeStateChanged(state); + } }); mMultiProfilePagerAdapter.setOnSwitchOnWorkSelectedListener( () -> { @@ -1661,6 +1689,8 @@ public class ResolverActivity extends Activity implements findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE); } + void onHorizontalSwipeStateChanged(int state) {} + private void maybeHideDivider() { if (!isIntentPicker()) { return; @@ -1801,6 +1831,12 @@ public class ResolverActivity extends Activity implements ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); View buttonBarDivider = findViewById(R.id.resolver_button_bar_divider); + if (!useLayoutWithDefault()) { + int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0; + buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(), + buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize( + R.dimen.resolver_button_bar_spacing) + inset); + } if (activeListAdapter.isTabLoaded() && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter) && !useLayoutWithDefault()) { @@ -1817,12 +1853,6 @@ public class ResolverActivity extends Activity implements buttonLayout.setVisibility(View.VISIBLE); setButtonBarIgnoreOffset(/* ignoreOffset */ true); - if (!useLayoutWithDefault()) { - int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0; - buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(), - buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize( - R.dimen.resolver_button_bar_spacing) + inset); - } mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once); mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always); @@ -1923,7 +1953,7 @@ public class ResolverActivity extends Activity implements ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); activeListAdapter.notifyDataSetChanged(); - if (activeListAdapter.getCount() == 0) { + if (activeListAdapter.getCount() == 0 && !inactiveListAdapterHasItems()) { // We no longer have any items... just finish the activity. finish(); } @@ -1933,23 +1963,42 @@ public class ResolverActivity extends Activity implements } } + private boolean inactiveListAdapterHasItems() { + if (!shouldShowTabs()) { + return false; + } + return mMultiProfilePagerAdapter.getInactiveListAdapter().getCount() > 0; + } + private BroadcastReceiver createWorkProfileStateReceiver() { return new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (!TextUtils.equals(action, Intent.ACTION_USER_UNLOCKED) - && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) { + && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) + && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_AVAILABLE)) { return; } - int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (TextUtils.equals(action, Intent.ACTION_USER_UNLOCKED) - && userHandle != getWorkProfileUserHandle().getIdentifier()) { + + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + + if (userId != getWorkProfileUserHandle().getIdentifier()) { return; } - if (TextUtils.equals(action, Intent.ACTION_USER_UNLOCKED)) { + + if (isWorkProfileEnabled()) { + if (mWorkProfileHasBeenEnabled) { + return; + } + + mWorkProfileHasBeenEnabled = true; mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived(); + } else { + // Must be an UNAVAILABLE broadcast, so we watch for the next availability + mWorkProfileHasBeenEnabled = false; } + if (mMultiProfilePagerAdapter.getCurrentUserHandle() .equals(getWorkProfileUserHandle())) { mMultiProfilePagerAdapter.rebuildActiveTab(true); diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 97f43d27a9ce1a53410070b350ea393f065ae3bd..eef722e32bdcde7bec2a4c74f91a4684a407762d 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -54,7 +54,6 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import com.android.internal.app.chooser.DisplayResolveInfo; -import com.android.internal.app.chooser.SelectableTargetInfo; import com.android.internal.app.chooser.TargetInfo; import java.util.ArrayList; @@ -68,7 +67,7 @@ public class ResolverListAdapter extends BaseAdapter { private final List mBaseResolveList; private final PackageManager mPm; protected final Context mContext; - private final ColorMatrixColorFilter mSuspendedMatrixColorFilter; + private static ColorMatrixColorFilter sSuspendedMatrixColorFilter; private final int mIconDpi; protected ResolveInfo mLastChosen; private DisplayResolveInfo mOtherProfile; @@ -103,7 +102,6 @@ public class ResolverListAdapter extends BaseAdapter { mDisplayList = new ArrayList<>(); mFilterLastUsed = filterLastUsed; mResolverListController = resolverListController; - mSuspendedMatrixColorFilter = createSuspendedColorMatrix(); mResolverListCommunicator = resolverListCommunicator; mIsAudioCaptureDevice = isAudioCaptureDevice; final ActivityManager am = (ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE); @@ -423,11 +421,12 @@ public class ResolverListAdapter extends BaseAdapter { // We assume that at this point we've already filtered out the only intent for a different // targetUserId which we're going to use. private void addResolveInfo(DisplayResolveInfo dri) { - // TODO(arangelov): Is that UserHandle.USER_CURRENT check okay? if (dri != null && dri.getResolveInfo() != null && dri.getResolveInfo().targetUserId == UserHandle.USER_CURRENT) { if (shouldAddResolveInfo(dri)) { mDisplayList.add(dri); + Log.i(TAG, "Add DisplayResolveInfo component: " + dri.getResolvedComponentName() + + ", intent component: " + dri.getResolvedIntent().getComponent()); } } } @@ -539,29 +538,14 @@ public class ResolverListAdapter extends BaseAdapter { && !((DisplayResolveInfo) info).hasDisplayLabel()) { getLoadLabelTask((DisplayResolveInfo) info, holder).execute(); } else { - holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo()); - if (info instanceof SelectableTargetInfo) { - // direct share targets should append the application name for a better readout - DisplayResolveInfo rInfo = ((SelectableTargetInfo) info).getDisplayResolveInfo(); - CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : ""; - CharSequence extendedInfo = info.getExtendedInfo(); - String contentDescription = String.join(" ", info.getDisplayLabel(), - extendedInfo != null ? extendedInfo : "", appName); - holder.updateContentDescription(contentDescription); - } - } - - if (info.isSuspended()) { - holder.icon.setColorFilter(mSuspendedMatrixColorFilter); - } else { - holder.icon.setColorFilter(null); + holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel()); } if (info instanceof DisplayResolveInfo && !((DisplayResolveInfo) info).hasDisplayIcon()) { - new ResolverListAdapter.LoadIconTask((DisplayResolveInfo) info, holder.icon).execute(); + new LoadIconTask((DisplayResolveInfo) info, holder).execute(); } else { - holder.icon.setImageDrawable(info.getDisplayIcon(mContext)); + holder.bindIcon(info); } } @@ -579,23 +563,27 @@ public class ResolverListAdapter extends BaseAdapter { } } - private ColorMatrixColorFilter createSuspendedColorMatrix() { - int grayValue = 127; - float scale = 0.5f; // half bright + private static ColorMatrixColorFilter getSuspendedColorMatrix() { + if (sSuspendedMatrixColorFilter == null) { + + int grayValue = 127; + float scale = 0.5f; // half bright - ColorMatrix tempBrightnessMatrix = new ColorMatrix(); - float[] mat = tempBrightnessMatrix.getArray(); - mat[0] = scale; - mat[6] = scale; - mat[12] = scale; - mat[4] = grayValue; - mat[9] = grayValue; - mat[14] = grayValue; + ColorMatrix tempBrightnessMatrix = new ColorMatrix(); + float[] mat = tempBrightnessMatrix.getArray(); + mat[0] = scale; + mat[6] = scale; + mat[12] = scale; + mat[4] = grayValue; + mat[9] = grayValue; + mat[14] = grayValue; - ColorMatrix matrix = new ColorMatrix(); - matrix.setSaturation(0.0f); - matrix.preConcat(tempBrightnessMatrix); - return new ColorMatrixColorFilter(matrix); + ColorMatrix matrix = new ColorMatrix(); + matrix.setSaturation(0.0f); + matrix.preConcat(tempBrightnessMatrix); + sSuspendedMatrixColorFilter = new ColorMatrixColorFilter(matrix); + } + return sSuspendedMatrixColorFilter; } ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) { @@ -614,11 +602,22 @@ public class ResolverListAdapter extends BaseAdapter { void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) { final DisplayResolveInfo iconInfo = getFilteredItem(); if (iconView != null && iconInfo != null) { - new LoadIconTask(iconInfo, iconView).execute(); + new AsyncTask() { + @Override + protected Drawable doInBackground(Void... params) { + return loadIconForResolveInfo(iconInfo.getResolveInfo()); + } + + @Override + protected void onPostExecute(Drawable d) { + iconView.setImageDrawable(d); + } + }.execute(); } } - UserHandle getUserHandle() { + @VisibleForTesting + public UserHandle getUserHandle() { return mResolverListController.getUserHandle(); } @@ -640,6 +639,10 @@ public class ResolverListAdapter extends BaseAdapter { mIsTabLoaded = true; } + protected boolean alwaysShowSubLabel() { + return false; + } + /** * Necessary methods to communicate between {@link ResolverListAdapter} * and {@link ResolverActivity}. @@ -682,20 +685,18 @@ public class ResolverListAdapter extends BaseAdapter { icon = (ImageView) view.findViewById(R.id.icon); } - public void bindLabel(CharSequence label, CharSequence subLabel) { - if (!TextUtils.equals(text.getText(), label)) { - text.setText(label); - } + public void bindLabel(CharSequence label, CharSequence subLabel, boolean showSubLabel) { + text.setText(label); - // Always show a subLabel for visual consistency across list items. Show an empty - // subLabel if the subLabel is the same as the label if (TextUtils.equals(label, subLabel)) { subLabel = null; } - if (!TextUtils.equals(text2.getText(), subLabel)) { + text2.setText(subLabel); + if (showSubLabel || subLabel != null) { text2.setVisibility(View.VISIBLE); - text2.setText(subLabel); + } else { + text2.setVisibility(View.GONE); } itemView.setContentDescription(null); @@ -704,6 +705,15 @@ public class ResolverListAdapter extends BaseAdapter { public void updateContentDescription(String description) { itemView.setContentDescription(description); } + + public void bindIcon(TargetInfo info) { + icon.setImageDrawable(info.getDisplayIcon(itemView.getContext())); + if (info.isSuspended()) { + icon.setColorFilter(getSuspendedColorMatrix()); + } else { + icon.setColorFilter(null); + } + } } protected class LoadLabelTask extends AsyncTask { @@ -752,19 +762,19 @@ public class ResolverListAdapter extends BaseAdapter { protected void onPostExecute(CharSequence[] result) { mDisplayResolveInfo.setDisplayLabel(result[0]); mDisplayResolveInfo.setExtendedInfo(result[1]); - mHolder.bindLabel(result[0], result[1]); + mHolder.bindLabel(result[0], result[1], alwaysShowSubLabel()); } } class LoadIconTask extends AsyncTask { - protected final com.android.internal.app.chooser.DisplayResolveInfo mDisplayResolveInfo; + protected final DisplayResolveInfo mDisplayResolveInfo; private final ResolveInfo mResolveInfo; - private final ImageView mTargetView; + private ViewHolder mHolder; - LoadIconTask(DisplayResolveInfo dri, ImageView target) { + LoadIconTask(DisplayResolveInfo dri, ViewHolder holder) { mDisplayResolveInfo = dri; mResolveInfo = dri.getResolveInfo(); - mTargetView = target; + mHolder = holder; } @Override @@ -778,9 +788,14 @@ public class ResolverListAdapter extends BaseAdapter { mResolverListCommunicator.updateProfileViewButton(); } else { mDisplayResolveInfo.setDisplayIcon(d); - mTargetView.setImageDrawable(d); + mHolder.bindIcon(mDisplayResolveInfo); } } + + public void setViewHolder(ViewHolder holder) { + mHolder = holder; + mHolder.bindIcon(mDisplayResolveInfo); + } } /** diff --git a/core/java/com/android/internal/app/ResolverViewPager.java b/core/java/com/android/internal/app/ResolverViewPager.java index 4eb6e3bd2071c5d1b01d462345949fa6f595a1e3..478cc18f13ee24fda28cf05d9048e34bc681c8c4 100644 --- a/core/java/com/android/internal/app/ResolverViewPager.java +++ b/core/java/com/android/internal/app/ResolverViewPager.java @@ -18,6 +18,7 @@ package com.android.internal.app; import android.content.Context; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import com.android.internal.widget.ViewPager; @@ -30,6 +31,8 @@ import com.android.internal.widget.ViewPager; */ public class ResolverViewPager extends ViewPager { + private boolean mSwipingEnabled = true; + public ResolverViewPager(Context context) { super(context); } @@ -70,4 +73,17 @@ public class ResolverViewPager extends ViewPager { heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + + /** + * Sets whether swiping sideways should happen. + *

Note that swiping is always disabled for RTL layouts (b/159110029 for context). + */ + void setSwipingEnabled(boolean swipingEnabled) { + mSwipingEnabled = swipingEnabled; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return !isLayoutRtl() && mSwipingEnabled && super.onInterceptTouchEvent(ev); + } } diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java index ffe2dbe4ccc065562ad2219dbac8ddd9b4b26973..2d91e64b2e6765c9d7a82a5f5b63fee37261c7dd 100644 --- a/core/java/com/android/internal/app/SimpleIconFactory.java +++ b/core/java/com/android/internal/app/SimpleIconFactory.java @@ -19,6 +19,7 @@ package com.android.internal.app; import static android.content.Context.ACTIVITY_SERVICE; import static android.graphics.Paint.DITHER_FLAG; import static android.graphics.Paint.FILTER_BITMAP_FLAG; +import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction; import android.annotation.AttrRes; import android.annotation.NonNull; @@ -54,6 +55,7 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import java.nio.ByteBuffer; +import java.util.Optional; /** @@ -64,6 +66,7 @@ import java.nio.ByteBuffer; @Deprecated public class SimpleIconFactory { + private static final SynchronizedPool sPool = new SynchronizedPool<>(Runtime.getRuntime().availableProcessors()); @@ -251,7 +254,7 @@ public class SimpleIconFactory { } else if (w > h && h > 0) { scale = (float) w / h; } - Bitmap bitmap = createIconBitmap(icon, scale); + Bitmap bitmap = createIconBitmapNoInsetOrMask(icon, scale); bitmap = maskBitmapToCircle(bitmap); icon = new BitmapDrawable(mContext.getResources(), bitmap); @@ -281,15 +284,19 @@ public class SimpleIconFactory { final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(output); - final Paint paint = new Paint(); - paint.setAntiAlias(true); + final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG + | Paint.FILTER_BITMAP_FLAG); + + // Apply an offset to enable shadow to be drawn + final int size = bitmap.getWidth(); + int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), 1); // Draw mask paint.setColor(0xffffffff); canvas.drawARGB(0, 0, 0, 0); canvas.drawCircle(bitmap.getWidth() / 2f, bitmap.getHeight() / 2f, - bitmap.getWidth() / 2f - 1 /* -1 to avoid circles with flat sides */, + bitmap.getWidth() / 2f - offset, paint); // Draw masked bitmap @@ -306,24 +313,61 @@ public class SimpleIconFactory { } private Bitmap createIconBitmap(Drawable icon, float scale) { - return createIconBitmap(icon, scale, mIconBitmapSize); + return createIconBitmap(icon, scale, mIconBitmapSize, true, false); + } + + private Bitmap createIconBitmapNoInsetOrMask(Drawable icon, float scale) { + return createIconBitmap(icon, scale, mIconBitmapSize, false, true); } /** * @param icon drawable that should be flattened to a bitmap * @param scale the scale to apply before drawing {@param icon} on the canvas + * @param insetAdiForShadow when rendering AdaptiveIconDrawables inset to make room for a shadow + * @param ignoreAdiMask when rendering AdaptiveIconDrawables ignore the current system mask */ - private Bitmap createIconBitmap(Drawable icon, float scale, int size) { + private Bitmap createIconBitmap(Drawable icon, float scale, int size, boolean insetAdiForShadow, + boolean ignoreAdiMask) { Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); mCanvas.setBitmap(bitmap); mOldBounds.set(icon.getBounds()); if (icon instanceof AdaptiveIconDrawable) { - int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), - Math.round(size * (1 - scale) / 2)); - icon.setBounds(offset, offset, size - offset, size - offset); - icon.draw(mCanvas); + final AdaptiveIconDrawable adi = (AdaptiveIconDrawable) icon; + + // By default assumes the output bitmap will have a shadow directly applied and makes + // room for it by insetting. If there are intermediate steps before applying the shadow + // insetting is disableable. + int offset = Math.round(size * (1 - scale) / 2); + if (insetAdiForShadow) { + offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), offset); + } + Rect bounds = new Rect(offset, offset, size - offset, size - offset); + + // AdaptiveIconDrawables are by default masked by the user's icon shape selection. + // If further masking is to be done, directly render to avoid the system masking. + if (ignoreAdiMask) { + final int cX = bounds.width() / 2; + final int cY = bounds.height() / 2; + final float portScale = 1f / (1 + 2 * getExtraInsetFraction()); + final int insetWidth = (int) (bounds.width() / (portScale * 2)); + final int insetHeight = (int) (bounds.height() / (portScale * 2)); + + Rect childRect = new Rect(cX - insetWidth, cY - insetHeight, cX + insetWidth, + cY + insetHeight); + Optional.ofNullable(adi.getBackground()).ifPresent(drawable -> { + drawable.setBounds(childRect); + drawable.draw(mCanvas); + }); + Optional.ofNullable(adi.getForeground()).ifPresent(drawable -> { + drawable.setBounds(childRect); + drawable.draw(mCanvas); + }); + } else { + adi.setBounds(bounds); + adi.draw(mCanvas); + } } else { if (icon instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java index 0589baa76b8a130f2bf883edf1b949e5ce8f52eb..d8eaeda2b5491a8ad2ebdafc143837734d854045 100644 --- a/core/java/com/android/internal/app/SuspendedAppActivity.java +++ b/core/java/com/android/internal/app/SuspendedAppActivity.java @@ -26,6 +26,7 @@ import android.Manifest; import android.annotation.Nullable; import android.app.AlertDialog; import android.app.AppGlobals; +import android.app.KeyguardManager; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; @@ -208,9 +209,32 @@ public class SuspendedAppActivity extends AlertActivity ap.mPositiveButtonText = getString(android.R.string.ok); ap.mNeutralButtonText = resolveNeutralButtonText(); ap.mPositiveButtonListener = ap.mNeutralButtonListener = this; + + requestDismissKeyguardIfNeeded(ap.mMessage); + setupAlert(); } + private void requestDismissKeyguardIfNeeded(CharSequence dismissMessage) { + final KeyguardManager km = getSystemService(KeyguardManager.class); + if (km.isKeyguardLocked()) { + km.requestDismissKeyguard(this, dismissMessage, + new KeyguardManager.KeyguardDismissCallback() { + @Override + public void onDismissError() { + Slog.e(TAG, "Error while dismissing keyguard." + + " Keeping the dialog visible."); + } + + @Override + public void onDismissCancelled() { + Slog.w(TAG, "Keyguard dismiss was cancelled. Finishing."); + SuspendedAppActivity.this.finish(); + } + }); + } + } + @Override public void onClick(DialogInterface dialog, int which) { switch (which) { diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java index 6f7695ce8c34ae112dd635c31fe0c7769f471b6f..ca0856238b90d1197868754e4390d1388924df13 100644 --- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java +++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java @@ -26,6 +26,8 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; @@ -97,7 +99,10 @@ public class UnlaunchableAppActivity extends Activity @Override public void onClick(DialogInterface dialog, int which) { if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE && which == DialogInterface.BUTTON_POSITIVE) { - UserManager.get(this).requestQuietModeEnabled(false, UserHandle.of(mUserId), mTarget); + UserManager userManager = UserManager.get(this); + new Handler(Looper.getMainLooper()).post( + () -> userManager.requestQuietModeEnabled( + /* enableQuietMode= */ false, UserHandle.of(mUserId), mTarget)); } } diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java index 86a9af3db1964324f7189300e5443b53c503e920..fe0e7d01226267e88260a4d3ed78a026a3119720 100644 --- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java +++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java @@ -40,8 +40,10 @@ import java.util.List; * resolve it to an activity. */ public class DisplayResolveInfo implements TargetInfo { - // Temporary flag for new chooser delegate behavior. - private static final boolean ENABLE_CHOOSER_DELEGATE = true; + // Temporary flag for new chooser delegate behavior. There are occassional token + // permission errors from bouncing through the delegate. Watch out before reenabling: + // b/157272342 is one example but this issue has been reported many times + private static final boolean ENABLE_CHOOSER_DELEGATE = false; private final ResolveInfo mResolveInfo; private CharSequence mDisplayLabel; diff --git a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java index e582583521061953b2bbb4e510b8b39c1ced17cc..cf921d734d48be1c1574d4923e86111aac84f91c 100644 --- a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java +++ b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java @@ -69,6 +69,13 @@ public class MultiDisplayResolveInfo extends DisplayResolveInfo { mSelected = selected; } + /** + * Return selected target. + */ + public DisplayResolveInfo getSelectedTarget() { + return hasSelected() ? mTargetInfos.get(mSelected) : null; + } + /** * Whether or not the user has selected a specific target for this MultiInfo. */ diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index b8142607ebd72eb1895136dab04d6c235666bfda..ab58fc0eeafc1ef8d68d3c0bb35139c7a7f45a27 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -49,12 +49,14 @@ import android.os.SystemClock; import android.os.UserHandle; import android.service.procstats.ProcessStatsProto; import android.service.procstats.ProcessStatsStateProto; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.Log; import android.util.LongSparseArray; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -1420,10 +1422,38 @@ public final class ProcessState { proto.end(token); } + /** + * Assume the atom already includes a UID field, write the process name only if + * it's different from the package name; and only write the suffix if possible. + */ + static void writeCompressedProcessName(final ProtoOutputStream proto, final long fieldId, + final String procName, final String packageName, final boolean sharedUid) { + if (sharedUid) { + // This UID has multiple packages running, write the full process name here + proto.write(fieldId, procName); + return; + } + if (TextUtils.equals(procName, packageName)) { + // Same name, don't bother to write the process name here. + return; + } + if (procName.startsWith(packageName)) { + final int pkgLength = packageName.length(); + if (procName.charAt(pkgLength) == ':') { + // Only write the suffix starting with ':' + proto.write(fieldId, procName.substring(pkgLength)); + return; + } + } + // Write the full process name + proto.write(fieldId, procName); + } + /** Similar to {@code #dumpDebug}, but with a reduced/aggregated subset of states. */ public void dumpAggregatedProtoForStatsd(ProtoOutputStream proto, long fieldId, String procName, int uid, long now, - final ProcessMap> procToPkgMap) { + final ProcessMap> procToPkgMap, + final SparseArray> uidToPkgMap) { // Group proc stats by aggregated type (only screen state + process state) SparseLongArray durationByState = new SparseLongArray(); boolean didCurState = false; @@ -1503,7 +1533,8 @@ public final class ProcessState { // build the output final long token = proto.start(fieldId); - proto.write(ProcessStatsProto.PROCESS, procName); + writeCompressedProcessName(proto, ProcessStatsProto.PROCESS, procName, mPackage, + mMultiPackage || (uidToPkgMap.get(mUid).size() > 1)); proto.write(ProcessStatsProto.UID, uid); for (int i = 0; i < durationByState.size(); i++) { @@ -1528,7 +1559,7 @@ public final class ProcessState { } mStats.dumpFilteredAssociationStatesProtoForProc(proto, ProcessStatsProto.ASSOCS, - now, this, procToPkgMap); + now, this, procToPkgMap, uidToPkgMap); proto.end(token); } } diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 928ba35595d387b190b1f269b09dac023ca28930..11e55b852516a40192bd6668ae99cdcbf3bbb80f 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -2232,22 +2232,43 @@ public final class ProcessStats implements Parcelable { } /** Similar to {@code #dumpDebug}, but with a reduced/aggregated subset of states. */ - public void dumpAggregatedProtoForStatsd(ProtoOutputStream proto) { - dumpProtoPreamble(proto); + public void dumpAggregatedProtoForStatsd(ProtoOutputStream[] protoStreams, + long maxRawShardSizeBytes) { + int shardIndex = 0; + dumpProtoPreamble(protoStreams[shardIndex]); + final ArrayMap> procMap = mProcesses.getMap(); - final ProcessMap> procToPkgMap = - collectProcessPackageMaps(null, false); + final ProcessMap> procToPkgMap = new ProcessMap<>(); + final SparseArray> uidToPkgMap = new SparseArray<>(); + collectProcessPackageMaps(null, false, procToPkgMap, uidToPkgMap); + for (int ip = 0; ip < procMap.size(); ip++) { final String procName = procMap.keyAt(ip); + if (protoStreams[shardIndex].getRawSize() > maxRawShardSizeBytes) { + shardIndex++; + if (shardIndex >= protoStreams.length) { + // We have run out of space; we'll drop the rest of the processes. + Slog.d(TAG, String.format("Dropping process indices from %d to %d from " + + "statsd proto (too large)", ip, procMap.size())); + break; + } + dumpProtoPreamble(protoStreams[shardIndex]); + } + final SparseArray uids = procMap.valueAt(ip); for (int iu = 0; iu < uids.size(); iu++) { final int uid = uids.keyAt(iu); final ProcessState procState = uids.valueAt(iu); - procState.dumpAggregatedProtoForStatsd(proto, + procState.dumpAggregatedProtoForStatsd(protoStreams[shardIndex], ProcessStatsSectionProto.PROCESS_STATS, - procName, uid, mTimePeriodEndRealtime, procToPkgMap); + procName, uid, mTimePeriodEndRealtime, + procToPkgMap, uidToPkgMap); } } + + for (int i = 0; i <= shardIndex; i++) { + protoStreams[i].flush(); + } } private void dumpProtoPreamble(ProtoOutputStream proto) { @@ -2279,10 +2300,9 @@ public final class ProcessStats implements Parcelable { /** * Walk through the known processes and build up the process -> packages map if necessary. */ - public ProcessMap> collectProcessPackageMaps( - String reqPackage, boolean activeOnly) { - final ProcessMap> map = new ProcessMap<>(); - + private void collectProcessPackageMaps(String reqPackage, boolean activeOnly, + final ProcessMap> procToPkgMap, + final SparseArray> uidToPkgMap) { final ArrayMap>> pkgMap = mPackages.getMap(); for (int ip = pkgMap.size() - 1; ip >= 0; ip--) { @@ -2304,17 +2324,22 @@ public final class ProcessStats implements Parcelable { final String name = proc.getName(); final int uid = proc.getUid(); - ArraySet pkgStates = map.get(name, uid); + ArraySet pkgStates = procToPkgMap.get(name, uid); if (pkgStates == null) { pkgStates = new ArraySet<>(); - map.put(name, uid, pkgStates); + procToPkgMap.put(name, uid, pkgStates); } pkgStates.add(state); + ArraySet packages = uidToPkgMap.get(uid); + if (packages == null) { + packages = new ArraySet<>(); + uidToPkgMap.put(uid, packages); + } + packages.add(state.mPackageName); } } } } - return map; } /** @@ -2329,10 +2354,12 @@ public final class ProcessStats implements Parcelable { * @param now The timestamp when the dump was initiated. * @param procState The target process where its association states should be dumped. * @param proc2Pkg The map between process to packages running within it. + * @param uidToPkgMap The map between UID to packages with this UID */ public void dumpFilteredAssociationStatesProtoForProc(ProtoOutputStream proto, long fieldId, long now, ProcessState procState, - final ProcessMap> proc2Pkg) { + final ProcessMap> proc2Pkg, + final SparseArray> uidToPkgMap) { if (procState.isMultiPackage() && procState.getCommonProcess() != procState) { // It's a per-package process state, don't bother to write into statsd return; @@ -2395,8 +2422,12 @@ public final class ProcessStats implements Parcelable { final SourceKey key = assocVals.keyAt(i); final long[] vals = assocVals.valueAt(i); final long token = proto.start(fieldId); - proto.write(ProcessStatsAssociationProto.ASSOC_PROCESS_NAME, key.mProcess); - proto.write(ProcessStatsAssociationProto.ASSOC_PACKAGE_NAME, key.mPackage); + final int idx = uidToPkgMap.indexOfKey(key.mUid); + ProcessState.writeCompressedProcessName(proto, + ProcessStatsAssociationProto.ASSOC_PROCESS_NAME, + key.mProcess, key.mPackage, + idx >= 0 && uidToPkgMap.valueAt(idx).size() > 1); + proto.write(ProcessStatsAssociationProto.ASSOC_UID, key.mUid); proto.write(ProcessStatsAssociationProto.TOTAL_COUNT, (int) vals[1]); proto.write(ProcessStatsAssociationProto.TOTAL_DURATION_SECS, (int) (vals[0] / 1000)); diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java index e9d7d05fe68d0f08f7e4a70f95d1930d9509ba0b..167d128a76f80ec85a4c5e1d192f292645aa5814 100644 --- a/core/java/com/android/internal/infra/ServiceConnector.java +++ b/core/java/com/android/internal/infra/ServiceConnector.java @@ -709,7 +709,7 @@ public interface ServiceConnector { if (DEBUG) { return mDebugName; } - return mDelegate.toString() + " wrapped into " + super.toString(); + return mDelegate + " wrapped into " + super.toString(); } @Override diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index a660493f4613b81a64429546e0785ba53b385ce8..085cdfcf946233f75de4b97770c027416f145bf6 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -46,8 +46,10 @@ public final class InputMethodDebug { return "UNSPECIFIED"; case StartInputReason.WINDOW_FOCUS_GAIN: return "WINDOW_FOCUS_GAIN"; - case StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY: - return "WINDOW_FOCUS_GAIN_REPORT_ONLY"; + case StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_SAME_EDITOR: + return "WINDOW_FOCUS_GAIN_REPORT_WITH_SAME_EDITOR"; + case StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_EDITOR: + return "WINDOW_FOCUS_GAIN_REPORT_WITHOUT_EDITOR"; case StartInputReason.APP_CALLED_RESTART_INPUT_API: return "APP_CALLED_RESTART_INPUT_API"; case StartInputReason.CHECK_FOCUS: diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index 79397b81ace7da59c1b4278d587b2df9094570b1..4b968b45f1224a21e53f07543c9d849fbfad1332 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -46,7 +46,8 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME, SoftInputShowHideReason.HIDE_DOCKED_STACK_ATTACHED, - SoftInputShowHideReason.HIDE_RECENTS_ANIMATION}) + SoftInputShowHideReason.HIDE_RECENTS_ANIMATION, + SoftInputShowHideReason.HIDE_BUBBLES}) public @interface SoftInputShowHideReason { /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */ int SHOW_SOFT_INPUT = 0; @@ -140,4 +141,10 @@ public @interface SoftInputShowHideReason { * intercept touch from app window. */ int HIDE_RECENTS_ANIMATION = 18; + + /** + * Hide soft input when {@link com.android.systemui.bubbles.BubbleController} is expanding, + * switching, or collapsing Bubbles. + */ + int HIDE_BUBBLES = 19; } diff --git a/core/java/com/android/internal/inputmethod/StartInputReason.java b/core/java/com/android/internal/inputmethod/StartInputReason.java index a01c45919b8fc14643b02bc1fea854be08948e33..946ce858c12dff8f40349b03ac5e2fa84a8313b4 100644 --- a/core/java/com/android/internal/inputmethod/StartInputReason.java +++ b/core/java/com/android/internal/inputmethod/StartInputReason.java @@ -30,7 +30,8 @@ import java.lang.annotation.Retention; @IntDef(value = { StartInputReason.UNSPECIFIED, StartInputReason.WINDOW_FOCUS_GAIN, - StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, + StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_SAME_EDITOR, + StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_EDITOR, StartInputReason.APP_CALLED_RESTART_INPUT_API, StartInputReason.CHECK_FOCUS, StartInputReason.BOUND_TO_IMMS, @@ -48,45 +49,51 @@ public @interface StartInputReason { * to (re)start a new connection. */ int WINDOW_FOCUS_GAIN = 1; + /** + * {@link android.view.Window} gained focus and the focused view is same as current served + * view and its input connection remains. {@link android.view.inputmethod.InputMethodManager} + * just reports this window focus change event to sync IME input target for system. + */ + int WINDOW_FOCUS_GAIN_REPORT_WITH_SAME_EDITOR = 2; /** * {@link android.view.Window} gained focus but there is no {@link android.view.View} that is * eligible to have IME focus. {@link android.view.inputmethod.InputMethodManager} just reports - * this window focus change event. + * this window focus change event for logging. */ - int WINDOW_FOCUS_GAIN_REPORT_ONLY = 2; + int WINDOW_FOCUS_GAIN_REPORT_WITHOUT_EDITOR = 3; /** * {@link android.view.inputmethod.InputMethodManager#restartInput(android.view.View)} is * either explicitly called by the application or indirectly called by some Framework class * (e.g. {@link android.widget.EditText}). */ - int APP_CALLED_RESTART_INPUT_API = 3; + int APP_CALLED_RESTART_INPUT_API = 4; /** * {@link android.view.View} requested a new connection because of view focus change. */ - int CHECK_FOCUS = 4; + int CHECK_FOCUS = 5; /** * {@link android.view.inputmethod.InputMethodManager} is responding to * {@link com.android.internal.view.IInputMethodClient#onBindMethod}. */ - int BOUND_TO_IMMS = 5; + int BOUND_TO_IMMS = 6; /** * {@link android.view.inputmethod.InputMethodManager} is responding to * {@link com.android.internal.view.IInputMethodClient#onUnbindMethod}. */ - int UNBOUND_FROM_IMMS = 6; + int UNBOUND_FROM_IMMS = 7; /** * {@link android.view.inputmethod.InputMethodManager} is responding to * {@link com.android.internal.view.IInputMethodClient#setActive}. */ - int ACTIVATED_BY_IMMS = 7; + int ACTIVATED_BY_IMMS = 8; /** * {@link android.view.inputmethod.InputMethodManager} is responding to * {@link com.android.internal.view.IInputMethodClient#setActive}. */ - int DEACTIVATED_BY_IMMS = 8; + int DEACTIVATED_BY_IMMS = 9; /** * {@link com.android.server.inputmethod.InputMethodManagerService} is responding to * {@link com.android.internal.view.IInputSessionCallback#sessionCreated}. */ - int SESSION_CREATED_BY_IME = 9; + int SESSION_CREATED_BY_IME = 10; } diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java index 67ffd4d9340410b25ab0dc9df8d3751b5b8cf3df..5212265f6c8a3faf8846c71c332dfeaa8cf108b3 100644 --- a/core/java/com/android/internal/logging/UiEventLogger.java +++ b/core/java/com/android/internal/logging/UiEventLogger.java @@ -60,4 +60,28 @@ public interface UiEventLogger { */ void logWithInstanceId(@NonNull UiEventEnum event, int uid, @Nullable String packageName, @Nullable InstanceId instance); + + /** + * Log an event with ranked-choice information along with package. + * Does nothing if event.getId() <= 0. + * @param event an enum implementing UiEventEnum interface. + * @param uid the uid of the relevant app, if known (0 otherwise). + * @param packageName the package name of the relevant app, if known (null otherwise). + * @param position the position picked. + */ + void logWithPosition(@NonNull UiEventEnum event, int uid, @Nullable String packageName, + int position); + + /** + * Log an event with ranked-choice information along with package and instance ID. + * Does nothing if event.getId() <= 0. + * @param event an enum implementing UiEventEnum interface. + * @param uid the uid of the relevant app, if known (0 otherwise). + * @param packageName the package name of the relevant app, if known (null otherwise). + * @param instance An identifier obtained from an InstanceIdSequence. If null, reduces to + * logWithPosition(). + * @param position the position picked. + */ + void logWithInstanceIdAndPosition(@NonNull UiEventEnum event, int uid, + @Nullable String packageName, @Nullable InstanceId instance, int position); } diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index 4d171ec8a3a82b229a2461ca095f4d8fb57c2fe7..c9156c13aae32ae60efbd6bd3f90042a195538ea 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -48,4 +48,31 @@ public class UiEventLoggerImpl implements UiEventLogger { log(event, uid, packageName); } } + + @Override + public void logWithPosition(UiEventEnum event, int uid, String packageName, int position) { + final int eventID = event.getId(); + if (eventID > 0) { + FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED, + /* event_id = 1 */ eventID, + /* package_name = 2 */ packageName, + /* instance_id = 3 */ 0, + /* position_picked = 4 */ position); + } + } + + @Override + public void logWithInstanceIdAndPosition(UiEventEnum event, int uid, String packageName, + InstanceId instance, int position) { + final int eventID = event.getId(); + if ((eventID > 0) && (instance != null)) { + FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED, + /* event_id = 1 */ eventID, + /* package_name = 2 */ packageName, + /* instance_id = 3 */ instance.getId(), + /* position_picked = 4 */ position); + } else { + logWithPosition(event, uid, packageName, position); + } + } } diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java index 180ab0810f5b6f4e87e9d45de8ad79ea3e52bf07..2d09434807a6449db83cefabadef9ad083145cd2 100644 --- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java +++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java @@ -35,13 +35,15 @@ public class UiEventLoggerFake implements UiEventLogger { public final int eventId; public final int uid; public final String packageName; - public final InstanceId instanceId; // Used only for WithInstanceId variant + public final InstanceId instanceId; // Used only for WithInstanceId variants + public final int position; // Used only for Position variants FakeUiEvent(int eventId, int uid, String packageName) { this.eventId = eventId; this.uid = uid; this.packageName = packageName; this.instanceId = null; + this.position = 0; } FakeUiEvent(int eventId, int uid, String packageName, InstanceId instanceId) { @@ -49,6 +51,15 @@ public class UiEventLoggerFake implements UiEventLogger { this.uid = uid; this.packageName = packageName; this.instanceId = instanceId; + this.position = 0; + } + + FakeUiEvent(int eventId, int uid, String packageName, InstanceId instanceId, int position) { + this.eventId = eventId; + this.uid = uid; + this.packageName = packageName; + this.instanceId = instanceId; + this.position = position; } } @@ -92,4 +103,21 @@ public class UiEventLoggerFake implements UiEventLogger { mLogs.add(new FakeUiEvent(eventId, uid, packageName, instance)); } } + + @Override + public void logWithPosition(UiEventEnum event, int uid, String packageName, int position) { + final int eventId = event.getId(); + if (eventId > 0) { + mLogs.add(new FakeUiEvent(eventId, uid, packageName, null, position)); + } + } + + @Override + public void logWithInstanceIdAndPosition(UiEventEnum event, int uid, String packageName, + InstanceId instance, int position) { + final int eventId = event.getId(); + if (eventId > 0) { + mLogs.add(new FakeUiEvent(eventId, uid, packageName, instance, position)); + } + } } diff --git a/core/java/com/android/internal/os/FuseAppLoop.java b/core/java/com/android/internal/os/FuseAppLoop.java index ab0cc3093b6dc4857331d8a75d5a6148b203ec6c..2393036b5a3898dc6999db53ced9ad7b76f2738c 100644 --- a/core/java/com/android/internal/os/FuseAppLoop.java +++ b/core/java/com/android/internal/os/FuseAppLoop.java @@ -210,7 +210,7 @@ public class FuseAppLoop implements Handler.Callback { if (mInstance != 0) { native_replySimple(mInstance, unique, FUSE_OK); } - mBytesMap.stopUsing(entry.getThreadId()); + mBytesMap.stopUsing(inode); recycleLocked(args); } break; @@ -270,7 +270,7 @@ public class FuseAppLoop implements Handler.Callback { if (mInstance != 0) { native_replyOpen(mInstance, unique, /* fh */ inode); entry.opened = true; - return mBytesMap.startUsing(entry.getThreadId()); + return mBytesMap.startUsing(inode); } } catch (ErrnoException error) { replySimpleLocked(unique, getError(error)); @@ -354,27 +354,27 @@ public class FuseAppLoop implements Handler.Callback { } /** - * Map between Thread ID and byte buffer. + * Map between inode and byte buffer. */ private static class BytesMap { final Map mEntries = new HashMap<>(); - byte[] startUsing(long threadId) { - BytesMapEntry entry = mEntries.get(threadId); + byte[] startUsing(long inode) { + BytesMapEntry entry = mEntries.get(inode); if (entry == null) { entry = new BytesMapEntry(); - mEntries.put(threadId, entry); + mEntries.put(inode, entry); } entry.counter++; return entry.bytes; } - void stopUsing(long threadId) { - final BytesMapEntry entry = mEntries.get(threadId); + void stopUsing(long inode) { + final BytesMapEntry entry = mEntries.get(inode); Objects.requireNonNull(entry); entry.counter--; if (entry.counter <= 0) { - mEntries.remove(threadId); + mEntries.remove(inode); } } diff --git a/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java b/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java index c11b939098c4aa19680d4bd2290ad5b16e5f3e07..69ca9922e81f6f36c7498864512433cce55b55fc 100644 --- a/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java +++ b/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java @@ -59,7 +59,7 @@ import java.util.Objects; * #getProcessCpuUsageDiffed()} result. * *

Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of - * WestWorld, because the thresholding should be done after diffing, not before. This is because of + * statsd, because the thresholding should be done after diffing, not before. This is because of * two issues with thresholding before diffing: * *

    diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 505a05eb9c234c450fb20450406430fe574a9c51..a7d9827855a2108ebdc8ebc80f9927a4d5d5df7e 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo; import android.net.Credentials; import android.net.LocalServerSocket; import android.net.LocalSocket; +import android.net.NetworkUtils; import android.os.FactoryTest; import android.os.IVold; import android.os.Process; @@ -286,6 +287,13 @@ public final class Zygote { private Zygote() {} + private static boolean containsInetGid(int[] gids) { + for (int i = 0; i < gids.length; i++) { + if (gids[i] == android.os.Process.INET_GID) return true; + } + return false; + } + /** * Forks a new VM instance. The current VM must have been started * with the -Xzygote flag. NOTE: new instance keeps all @@ -341,6 +349,11 @@ public final class Zygote { if (pid == 0) { // Note that this event ends at the end of handleChildProc, Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork"); + + // If no GIDs were specified, don't make any permissions changes based on groups. + if (gids != null && gids.length > 0) { + NetworkUtils.setAllowNetworkingForProcess(containsInetGid(gids)); + } } // Set the Java Language thread priority to the default value for new apps. diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java index 7bfed91c42b921513c1f9c01983b286fce8e1510..6fe1d8140371ee2c406d8d4c1884c5e285118d19 100644 --- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java +++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java @@ -16,6 +16,7 @@ package com.android.internal.policy; +import android.graphics.Insets; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; @@ -69,16 +70,14 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame private ColorDrawable mNavigationBarColor; private boolean mOldFullscreen; private boolean mFullscreen; - private final Rect mOldSystemInsets = new Rect(); - private final Rect mOldStableInsets = new Rect(); - private final Rect mSystemInsets = new Rect(); - private final Rect mStableInsets = new Rect(); + private final Rect mOldSystemBarInsets = new Rect(); + private final Rect mSystemBarInsets = new Rect(); private final Rect mTmpRect = new Rect(); public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor, - boolean fullscreen, Rect systemInsets, Rect stableInsets) { + boolean fullscreen, Insets systemBarInsets) { setName("ResizeFrame"); mRenderer = renderer; @@ -95,10 +94,8 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame mTargetRect.set(initialBounds); mFullscreen = fullscreen; mOldFullscreen = fullscreen; - mSystemInsets.set(systemInsets); - mStableInsets.set(stableInsets); - mOldSystemInsets.set(systemInsets); - mOldStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets.toRect()); + mOldSystemBarInsets.set(systemBarInsets.toRect()); // Kick off our draw thread. start(); @@ -154,16 +151,13 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame * * @param newTargetBounds The new target bounds. * @param fullscreen Whether the window is currently drawing in fullscreen. - * @param systemInsets The current visible system insets for the window. - * @param stableInsets The stable insets for the window. + * @param systemBarInsets The current visible system insets for the window. */ - public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemInsets, - Rect stableInsets) { + public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemBarInsets) { synchronized (this) { mFullscreen = fullscreen; mTargetRect.set(newTargetBounds); - mSystemInsets.set(systemInsets); - mStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets); // Notify of a bounds change. pingRenderLocked(false /* drawImmediate */); } @@ -247,14 +241,12 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame mNewTargetRect.set(mTargetRect); if (!mNewTargetRect.equals(mOldTargetRect) || mOldFullscreen != mFullscreen - || !mStableInsets.equals(mOldStableInsets) - || !mSystemInsets.equals(mOldSystemInsets) + || !mSystemBarInsets.equals(mOldSystemBarInsets) || mReportNextDraw) { mOldFullscreen = mFullscreen; mOldTargetRect.set(mNewTargetRect); - mOldSystemInsets.set(mSystemInsets); - mOldStableInsets.set(mStableInsets); - redrawLocked(mNewTargetRect, mFullscreen, mSystemInsets, mStableInsets); + mOldSystemBarInsets.set(mSystemBarInsets); + redrawLocked(mNewTargetRect, mFullscreen); } } @@ -304,11 +296,8 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame * * @param newBounds The window bounds which needs to be drawn. * @param fullscreen Whether the window is currently drawing in fullscreen. - * @param systemInsets The current visible system insets for the window. - * @param stableInsets The stable insets for the window. */ - private void redrawLocked(Rect newBounds, boolean fullscreen, Rect systemInsets, - Rect stableInsets) { + private void redrawLocked(Rect newBounds, boolean fullscreen) { // While a configuration change is taking place the view hierarchy might become // inaccessible. For that case we remember the previous metrics to avoid flashes. @@ -355,7 +344,7 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame } mFrameAndBackdropNode.endRecording(); - drawColorViews(left, top, width, height, fullscreen, systemInsets, stableInsets); + drawColorViews(left, top, width, height, fullscreen); // We need to render the node explicitly mRenderer.drawRenderNode(mFrameAndBackdropNode); @@ -363,14 +352,13 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame reportDrawIfNeeded(); } - private void drawColorViews(int left, int top, int width, int height, - boolean fullscreen, Rect systemInsets, Rect stableInsets) { + private void drawColorViews(int left, int top, int width, int height, boolean fullscreen) { if (mSystemBarBackgroundNode == null) { return; } RecordingCanvas canvas = mSystemBarBackgroundNode.beginRecording(width, height); mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height); - final int topInset = DecorView.getColorViewTopInset(mStableInsets.top, mSystemInsets.top); + final int topInset = mSystemBarInsets.top; if (mStatusBarColor != null) { mStatusBarColor.setBounds(0, 0, left + width, topInset); mStatusBarColor.draw(canvas); @@ -380,7 +368,7 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame // don't want the navigation bar background be moving around when resizing in docked mode. // However, we need it for the transitions into/out of docked mode. if (mNavigationBarColor != null && fullscreen) { - DecorView.getNavigationBarRect(width, height, stableInsets, systemInsets, mTmpRect, 1f); + DecorView.getNavigationBarRect(width, height, mSystemBarInsets, mTmpRect, 1f); mNavigationBarColor.setBounds(mTmpRect); mNavigationBarColor.draw(canvas); } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index c6135f2c81d3f57861f5a1b454f6fc8601bbf3d4..b12c5e9ba5b056fb42034301c6f5861e44dfbb08 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1050,22 +1050,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return false; } - public static int getColorViewTopInset(int stableTop, int systemTop) { - return Math.min(stableTop, systemTop); - } - - public static int getColorViewBottomInset(int stableBottom, int systemBottom) { - return Math.min(stableBottom, systemBottom); - } - - public static int getColorViewRightInset(int stableRight, int systemRight) { - return Math.min(stableRight, systemRight); - } - - public static int getColorViewLeftInset(int stableLeft, int systemLeft) { - return Math.min(stableLeft, systemLeft); - } - public static boolean isNavBarToRightEdge(int bottomInset, int rightInset) { return bottomInset == 0 && rightInset > 0; } @@ -1079,14 +1063,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind : isNavBarToLeftEdge(bottomInset, leftInset) ? leftInset : bottomInset; } - public static void getNavigationBarRect(int canvasWidth, int canvasHeight, Rect stableInsets, - Rect contentInsets, Rect outRect, float scale) { - final int bottomInset = - (int) (getColorViewBottomInset(stableInsets.bottom, contentInsets.bottom) * scale); - final int leftInset = - (int) (getColorViewLeftInset(stableInsets.left, contentInsets.left) * scale); - final int rightInset = - (int) (getColorViewLeftInset(stableInsets.right, contentInsets.right) * scale); + public static void getNavigationBarRect(int canvasWidth, int canvasHeight, Rect systemBarInsets, + Rect outRect, float scale) { + final int bottomInset = (int) (systemBarInsets.bottom * scale); + final int leftInset = (int) (systemBarInsets.left * scale); + final int rightInset = (int) (systemBarInsets.right * scale); final int size = getNavBarSize(bottomInset, rightInset, leftInset); if (isNavBarToRightEdge(bottomInset, rightInset)) { outRect.set(canvasWidth - size, 0, canvasWidth, canvasHeight); @@ -1113,31 +1094,30 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mLastWindowFlags = attrs.flags; if (insets != null) { - mLastTopInset = getColorViewTopInset(insets.getStableInsetTop(), - insets.getSystemWindowInsetTop()); - mLastBottomInset = getColorViewBottomInset(insets.getStableInsetBottom(), - insets.getSystemWindowInsetBottom()); - mLastRightInset = getColorViewRightInset(insets.getStableInsetRight(), - insets.getSystemWindowInsetRight()); - mLastLeftInset = getColorViewRightInset(insets.getStableInsetLeft(), - insets.getSystemWindowInsetLeft()); + final Insets systemBarInsets = insets.getInsets(WindowInsets.Type.systemBars()); + final Insets stableBarInsets = insets.getInsetsIgnoringVisibility( + WindowInsets.Type.systemBars()); + mLastTopInset = systemBarInsets.top; + mLastBottomInset = systemBarInsets.bottom; + mLastRightInset = systemBarInsets.right; + mLastLeftInset = systemBarInsets.left; // Don't animate if the presence of stable insets has changed, because that // indicates that the window was either just added and received them for the // first time, or the window size or position has changed. - boolean hasTopStableInset = insets.getStableInsetTop() != 0; + boolean hasTopStableInset = stableBarInsets.top != 0; disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset); mLastHasTopStableInset = hasTopStableInset; - boolean hasBottomStableInset = insets.getStableInsetBottom() != 0; + boolean hasBottomStableInset = stableBarInsets.bottom != 0; disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset); mLastHasBottomStableInset = hasBottomStableInset; - boolean hasRightStableInset = insets.getStableInsetRight() != 0; + boolean hasRightStableInset = stableBarInsets.right != 0; disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset); mLastHasRightStableInset = hasRightStableInset; - boolean hasLeftStableInset = insets.getStableInsetLeft() != 0; + boolean hasLeftStableInset = stableBarInsets.left != 0; disallowAnimate |= (hasLeftStableInset != mLastHasLeftStableInset); mLastHasLeftStableInset = hasLeftStableInset; @@ -2296,7 +2276,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets) { if (mBackdropFrameRenderer != null) { - mBackdropFrameRenderer.setTargetRect(newBounds, fullscreen, systemInsets, stableInsets); + mBackdropFrameRenderer.setTargetRect(newBounds, fullscreen, systemInsets); } } @@ -2314,11 +2294,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final ThreadedRenderer renderer = getThreadedRenderer(); if (renderer != null) { loadBackgroundDrawablesIfNeeded(); + WindowInsets rootInsets = getRootWindowInsets(); mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer, initialBounds, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), - getCurrentColor(mNavigationColorViewState), fullscreen, systemInsets, - stableInsets); + getCurrentColor(mNavigationColorViewState), fullscreen, + rootInsets.getInsets(WindowInsets.Type.systemBars())); // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time. // If we want to get the shadow shown while resizing, we would need to elevate a new diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java index 575a5320bbd3ce4d43fe25c8615ffff24bb4d442..527286cf000ef8f0958f42bd761e2a2ed29c8816 100644 --- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java @@ -104,17 +104,18 @@ public class DividerSnapAlgorithm { public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision, Rect insets) { this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, - DOCKED_INVALID, false); + DOCKED_INVALID, false /* minimized */, true /* resizable */); } public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision, Rect insets, int dockSide) { this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, - dockSide, false); + dockSide, false /* minimized */, true /* resizable */); } public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, - boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode) { + boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode, + boolean isHomeResizable) { mMinFlingVelocityPxPerSecond = MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density; mMinDismissVelocityPxPerSecond = @@ -132,8 +133,8 @@ public class DividerSnapAlgorithm { com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1); mMinimalSizeResizableTask = res.getDimensionPixelSize( com.android.internal.R.dimen.default_minimal_size_resizable_task); - mTaskHeightInMinimizedMode = res.getDimensionPixelSize( - com.android.internal.R.dimen.task_height_of_minimized_mode); + mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize( + com.android.internal.R.dimen.task_height_of_minimized_mode) : 0; calculateTargets(isHorizontalDivision, dockSide); mFirstSplitTarget = mTargets.get(1); mLastSplitTarget = mTargets.get(mTargets.size() - 2); diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index c5729b05c5878fc10aa755831708c1ac32cb26ae..046981cf2e8f92ee171dd8eda07f6a492f488ad7 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -21,8 +21,6 @@ import static android.provider.Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COM import static android.view.View.SYSTEM_UI_LAYOUT_FLAGS; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static android.view.WindowInsets.Type.ime; -import static android.view.WindowInsets.Type.systemBars; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; @@ -34,8 +32,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import android.annotation.NonNull; import android.annotation.Nullable; @@ -146,17 +142,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if ((view.getWindowSystemUiVisibility() & SYSTEM_UI_LAYOUT_FLAGS) != 0) { return new Pair<>(Insets.NONE, insets); } - - boolean includeIme = (view.getViewRootImpl().mWindowAttributes.softInputMode - & SOFT_INPUT_MASK_ADJUST) - == SOFT_INPUT_ADJUST_RESIZE; - Insets insetsToApply; - if (ViewRootImpl.sNewInsetsMode == 0) { - insetsToApply = insets.getSystemWindowInsets(); - } else { - insetsToApply = insets.getInsets(systemBars() | (includeIme ? ime() : 0)); - } - insets = insets.inset(insetsToApply); + Insets insetsToApply = insets.getSystemWindowInsets(); return new Pair<>(insetsToApply, insets.inset(insetsToApply).consumeSystemWindowInsets()); }; diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index c32082418bc59829e949cc8252f0d5d646ac88d8..4999ec0556087e41bad0a8c8a98949c5358767f6 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -79,6 +79,7 @@ interface IStatusBarService void onNotificationSettingsViewed(String key); void onNotificationBubbleChanged(String key, boolean isBubble, int flags); void onBubbleNotificationSuppressionChanged(String key, boolean isSuppressed); + void hideCurrentInputMethodForBubbles(); void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName); void clearInlineReplyUriPermissions(String key); diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index ad6c7e8f7f60d6f933ad22c81a6223e03cd39911..9bf05135c4c5ccb7e859b5416402cc4855310742 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -8,10 +8,10 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -37,10 +37,12 @@ public class ScreenshotHelper { private int mSource; private boolean mHasStatusBar; private boolean mHasNavBar; - private Bitmap mBitmap; + private Bundle mBitmapBundle; private Rect mBoundsInScreen; private Insets mInsets; private int mTaskId; + private int mUserId; + private ComponentName mTopComponent; ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) { mSource = source; @@ -48,24 +50,29 @@ public class ScreenshotHelper { mHasNavBar = hasNav; } - ScreenshotRequest( - int source, Bitmap bitmap, Rect boundsInScreen, Insets insets, int taskId) { + ScreenshotRequest(int source, Bundle bitmapBundle, Rect boundsInScreen, Insets insets, + int taskId, int userId, ComponentName topComponent) { mSource = source; - mBitmap = bitmap; + mBitmapBundle = bitmapBundle; mBoundsInScreen = boundsInScreen; mInsets = insets; mTaskId = taskId; + mUserId = userId; + mTopComponent = topComponent; } ScreenshotRequest(Parcel in) { mSource = in.readInt(); mHasStatusBar = in.readBoolean(); mHasNavBar = in.readBoolean(); + if (in.readInt() == 1) { - mBitmap = in.readParcelable(Bitmap.class.getClassLoader()); + mBitmapBundle = in.readBundle(getClass().getClassLoader()); mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader()); mInsets = in.readParcelable(Insets.class.getClassLoader()); mTaskId = in.readInt(); + mUserId = in.readInt(); + mTopComponent = in.readParcelable(ComponentName.class.getClassLoader()); } } @@ -81,8 +88,8 @@ public class ScreenshotHelper { return mHasNavBar; } - public Bitmap getBitmap() { - return mBitmap; + public Bundle getBitmapBundle() { + return mBitmapBundle; } public Rect getBoundsInScreen() { @@ -97,6 +104,15 @@ public class ScreenshotHelper { return mTaskId; } + + public int getUserId() { + return mUserId; + } + + public ComponentName getTopComponent() { + return mTopComponent; + } + @Override public int describeContents() { return 0; @@ -107,14 +123,16 @@ public class ScreenshotHelper { dest.writeInt(mSource); dest.writeBoolean(mHasStatusBar); dest.writeBoolean(mHasNavBar); - if (mBitmap == null) { + if (mBitmapBundle == null) { dest.writeInt(0); } else { dest.writeInt(1); - dest.writeParcelable(mBitmap, 0); + dest.writeBundle(mBitmapBundle); dest.writeParcelable(mBoundsInScreen, 0); dest.writeParcelable(mInsets, 0); dest.writeInt(mTaskId); + dest.writeInt(mUserId); + dest.writeParcelable(mTopComponent, 0); } } @@ -234,19 +252,22 @@ public class ScreenshotHelper { /** * Request that provided image be handled as if it was a screenshot. * - * @param screenshot The bitmap to treat as the screen shot. + * @param screenshotBundle Bundle containing the buffer and color space of the screenshot. * @param boundsInScreen The bounds in screen coordinates that the bitmap orginated from. * @param insets The insets that the image was shown with, inside the screenbounds. * @param taskId The taskId of the task that the screen shot was taken of. + * @param userId The userId of user running the task provided in taskId. + * @param topComponent The component name of the top component running in the task. * @param handler A handler used in case the screenshot times out * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the * screenshot was taken. */ - public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen, - @NonNull Insets insets, int taskId, int source, + public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen, + @NonNull Insets insets, int taskId, int userId, ComponentName topComponent, int source, @NonNull Handler handler, @Nullable Consumer completionConsumer) { ScreenshotRequest screenshotRequest = - new ScreenshotRequest(source, screenshot, boundsInScreen, insets, taskId); + new ScreenshotRequest(source, screenshotBundle, boundsInScreen, insets, taskId, + userId, topComponent); takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest, completionConsumer); } @@ -295,7 +316,7 @@ public class ScreenshotHelper { }; msg.replyTo = new Messenger(h); - if (mScreenshotConnection == null) { + if (mScreenshotConnection == null || mScreenshotService == null) { final ComponentName serviceComponent = ComponentName.unflattenFromString( mContext.getResources().getString( com.android.internal.R.string.config_screenshotServiceComponent)); @@ -330,8 +351,11 @@ public class ScreenshotHelper { mContext.unbindService(mScreenshotConnection); mScreenshotConnection = null; mScreenshotService = null; - handler.removeCallbacks(mScreenshotTimeout); - notifyScreenshotError(); + // only log an error if we're still within the timeout period + if (handler.hasCallbacks(mScreenshotTimeout)) { + handler.removeCallbacks(mScreenshotTimeout); + notifyScreenshotError(); + } } } } diff --git a/core/java/com/android/internal/util/SyncResultReceiver.java b/core/java/com/android/internal/util/SyncResultReceiver.java index 00e91017a9ebc7ce6cd104042989bac4c6ed76cf..6b1358293d02e34fcbbd090e027d7a88dc735186 100644 --- a/core/java/com/android/internal/util/SyncResultReceiver.java +++ b/core/java/com/android/internal/util/SyncResultReceiver.java @@ -182,7 +182,7 @@ public final class SyncResultReceiver extends IResultReceiver.Stub { } /** @hide */ - public static final class TimeoutException extends RuntimeException { + public static final class TimeoutException extends Exception { private TimeoutException(String msg) { super(msg); } diff --git a/core/java/com/android/internal/view/IInlineSuggestionsRequestCallback.aidl b/core/java/com/android/internal/view/IInlineSuggestionsRequestCallback.aidl index cf1220c08467c91384d73da93facfa30ab020eeb..6c97962ac057205b4674057dfc0151f622592bf4 100644 --- a/core/java/com/android/internal/view/IInlineSuggestionsRequestCallback.aidl +++ b/core/java/com/android/internal/view/IInlineSuggestionsRequestCallback.aidl @@ -22,40 +22,56 @@ import android.view.inputmethod.InlineSuggestionsRequest; import com.android.internal.view.IInlineSuggestionsResponseCallback; /** - * Binder interface for the IME service to send an inline suggestion request to the system. + * Binder interface for the IME service to send {@link InlineSuggestionsRequest} or notify other IME + * service events to the system. * {@hide} */ oneway interface IInlineSuggestionsRequestCallback { - // Indicates that the current IME does not support inline suggestion. + /** Indicates that the current IME does not support inline suggestion. */ void onInlineSuggestionsUnsupported(); - // Sends the inline suggestions request from IME to Autofill. Calling this method indicates - // that the IME input is started on the view corresponding to the request. + /** + * Sends the inline suggestions request from IME to Autofill. Calling this method indicates + * that the IME input is started on the view corresponding to the request. + */ void onInlineSuggestionsRequest(in InlineSuggestionsRequest request, in IInlineSuggestionsResponseCallback callback); - // Signals that {@link android.inputmethodservice.InputMethodService - // #onStartInput(EditorInfo, boolean)} is called on the given focused field. + /** + * Signals that {@link android.inputmethodservice.InputMethodService + * #onStartInput(EditorInfo, boolean)} is called on the given focused field. + */ void onInputMethodStartInput(in AutofillId imeFieldId); - // Signals that {@link android.inputmethodservice.InputMethodService - // #dispatchOnShowInputRequested(int, boolean)} is called and shares the call result. - // The true value of {@code requestResult} means the IME is about to be shown, while - // false value means the IME will not be shown. + /** + * Signals that {@link android.inputmethodservice.InputMethodService + * #dispatchOnShowInputRequested(int, boolean)} is called and shares the call result. + * The true value of {@code requestResult} means the IME is about to be shown, while + * false value means the IME will not be shown. + */ void onInputMethodShowInputRequested(boolean requestResult); - // Signals that {@link android.inputmethodservice.InputMethodService - // #onStartInputView(EditorInfo, boolean)} is called on the field specified by the earlier - // {@link #onInputMethodStartInput(AutofillId)}. + /** + * Signals that {@link android.inputmethodservice.InputMethodService + * #onStartInputView(EditorInfo, boolean)} is called on the field specified by the earlier + * {@link #onInputMethodStartInput(AutofillId)}. + */ void onInputMethodStartInputView(); - // Signals that {@link android.inputmethodservice.InputMethodService - // #onFinishInputView(boolean)} is called on the field specified by the earlier - // {@link #onInputMethodStartInput(AutofillId)}. + /** + * Signals that {@link android.inputmethodservice.InputMethodService + * #onFinishInputView(boolean)} is called on the field specified by the earlier + * {@link #onInputMethodStartInput(AutofillId)}. + */ void onInputMethodFinishInputView(); - // Signals that {@link android.inputmethodservice.InputMethodService - // #onFinishInput()} is called on the field specified by the earlier - // {@link #onInputMethodStartInput(AutofillId)}. + /** + * Signals that {@link android.inputmethodservice.InputMethodService + * #onFinishInput()} is called on the field specified by the earlier + * {@link #onInputMethodStartInput(AutofillId)}. + */ void onInputMethodFinishInput(); + + // Indicates that the current IME changes inline suggestion session. + void onInlineSuggestionsSessionInvalidated(); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 3f03f2a3e75450ca0aedd578529f12c5df1af9ab..a1cbd3fcae79ae3c7201570216c5b96bbbb18305 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -71,4 +71,10 @@ interface IInputMethodManager { void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, in float[] matrixValues); + + oneway void reportPerceptible(in IBinder windowToken, boolean perceptible); + /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */ + void removeImeSurface(); + /** Remove the IME surface. Requires passing the currently focused window. */ + void removeImeSurfaceFromWindow(in IBinder windowToken); } diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 688e00bc5a2993537241c5d419adc25fa43e394a..0791ed3c42eca5395e9dedd882011c40987f4a8c 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -21,6 +21,10 @@ import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_ import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN; import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,6 +40,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; +import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Parcelable; @@ -67,6 +72,7 @@ import com.android.internal.util.ContrastColorUtil; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.function.Consumer; import java.util.regex.Pattern; @@ -80,7 +86,7 @@ public class ConversationLayout extends FrameLayout private static final float COLOR_SHIFT_AMOUNT = 60; /** - * Pattren for filter some ingonable characters. + * Pattern for filter some ignorable characters. * p{Z} for any kind of whitespace or invisible separator. * p{C} for any kind of punctuation character. */ @@ -93,8 +99,12 @@ public class ConversationLayout extends FrameLayout public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f); public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); + public static final Interpolator OVERSHOOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f); public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR = new MessagingPropertyAnimator(); + public static final int IMPORTANCE_ANIM_GROW_DURATION = 250; + public static final int IMPORTANCE_ANIM_SHRINK_DURATION = 200; + public static final int IMPORTANCE_ANIM_SHRINK_DELAY = 25; private List mMessages = new ArrayList<>(); private List mHistoricMessages = new ArrayList<>(); private MessagingLinearLayout mMessagingLinearLayout; @@ -161,6 +171,7 @@ public class ConversationLayout extends FrameLayout private Rect mAppOpsTouchRect = new Rect(); private float mMinTouchSize; private Icon mConversationIcon; + private Icon mShortcutIcon; private View mAppNameDivider; public ConversationLayout(@NonNull Context context) { @@ -186,7 +197,6 @@ public class ConversationLayout extends FrameLayout super.onFinishInflate(); mMessagingLinearLayout = findViewById(R.id.notification_messaging); mActions = findViewById(R.id.actions); - mMessagingLinearLayout.setMessagingLayout(this); mImageMessageContainer = findViewById(R.id.conversation_image_message_container); // We still want to clip, but only on the top, since views can temporarily out of bounds // during transitions. @@ -222,13 +232,20 @@ public class ConversationLayout extends FrameLayout oldVisibility = mImportanceRingView.getVisibility(); wasGone = oldVisibility == GONE; visibility = !mImportantConversation ? GONE : visibility; - isGone = visibility == GONE; - if (wasGone != isGone) { + boolean isRingGone = visibility == GONE; + if (wasGone != isRingGone) { // Keep the badge visibility in sync with the icon. This is necessary in cases // Where the icon is being hidden externally like in group children. mImportanceRingView.animate().cancel(); mImportanceRingView.setVisibility(visibility); } + + oldVisibility = mConversationIconBadge.getVisibility(); + wasGone = oldVisibility == GONE; + if (wasGone != isGone) { + mConversationIconBadge.animate().cancel(); + mConversationIconBadge.setVisibility(visibility); + } }); // When the small icon is gone, hide the rest of the badge mIcon.setOnForceHiddenChangedListener((forceHidden) -> { @@ -330,14 +347,74 @@ public class ConversationLayout extends FrameLayout mNameReplacement = nameReplacement; } - /** - * Sets this conversation as "important", adding some additional UI treatment. - */ + /** Sets this conversation as "important", adding some additional UI treatment. */ @RemotableViewMethod public void setIsImportantConversation(boolean isImportantConversation) { + setIsImportantConversation(isImportantConversation, false); + } + + /** @hide **/ + public void setIsImportantConversation(boolean isImportantConversation, boolean animate) { mImportantConversation = isImportantConversation; - mImportanceRingView.setVisibility(isImportantConversation - && mIcon.getVisibility() != GONE ? VISIBLE : GONE); + mImportanceRingView.setVisibility(isImportantConversation && mIcon.getVisibility() != GONE + ? VISIBLE : GONE); + + if (animate && isImportantConversation) { + GradientDrawable ring = (GradientDrawable) mImportanceRingView.getDrawable(); + ring.mutate(); + GradientDrawable bg = (GradientDrawable) mConversationIconBadgeBg.getDrawable(); + bg.mutate(); + int ringColor = getResources() + .getColor(R.color.conversation_important_highlight); + int standardThickness = getResources() + .getDimensionPixelSize(R.dimen.importance_ring_stroke_width); + int largeThickness = getResources() + .getDimensionPixelSize(R.dimen.importance_ring_anim_max_stroke_width); + int standardSize = getResources().getDimensionPixelSize( + R.dimen.importance_ring_size); + int baseSize = standardSize - standardThickness * 2; + int bgSize = getResources() + .getDimensionPixelSize(R.dimen.conversation_icon_size_badged); + + ValueAnimator.AnimatorUpdateListener animatorUpdateListener = animation -> { + int strokeWidth = Math.round((float) animation.getAnimatedValue()); + ring.setStroke(strokeWidth, ringColor); + int newSize = baseSize + strokeWidth * 2; + ring.setSize(newSize, newSize); + mImportanceRingView.invalidate(); + }; + + ValueAnimator growAnimation = ValueAnimator.ofFloat(0, largeThickness); + growAnimation.setInterpolator(LINEAR_OUT_SLOW_IN); + growAnimation.setDuration(IMPORTANCE_ANIM_GROW_DURATION); + growAnimation.addUpdateListener(animatorUpdateListener); + + ValueAnimator shrinkAnimation = + ValueAnimator.ofFloat(largeThickness, standardThickness); + shrinkAnimation.setDuration(IMPORTANCE_ANIM_SHRINK_DURATION); + shrinkAnimation.setStartDelay(IMPORTANCE_ANIM_SHRINK_DELAY); + shrinkAnimation.setInterpolator(OVERSHOOT); + shrinkAnimation.addUpdateListener(animatorUpdateListener); + shrinkAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + // Shrink the badge bg so that it doesn't peek behind the animation + bg.setSize(baseSize, baseSize); + mConversationIconBadgeBg.invalidate(); + } + + @Override + public void onAnimationEnd(Animator animation) { + // Reset bg back to normal size + bg.setSize(bgSize, bgSize); + mConversationIconBadgeBg.invalidate(); + } + }); + + AnimatorSet anims = new AnimatorSet(); + anims.playSequentially(growAnimation, shrinkAnimation); + anims.start(); + } } public boolean isImportantConversation() { @@ -465,10 +542,9 @@ public class ConversationLayout extends FrameLayout private void updateConversationLayout() { // Set avatar and name CharSequence conversationText = mConversationTitle; + mConversationIcon = mShortcutIcon; if (mIsOneToOne) { // Let's resolve the icon / text from the last sender - mConversationIconView.setVisibility(VISIBLE); - mConversationFacePile.setVisibility(GONE); CharSequence userKey = getKey(mUser); for (int i = mGroups.size() - 1; i >= 0; i--) { MessagingGroup messagingGroup = mGroups.get(i); @@ -480,30 +556,31 @@ public class ConversationLayout extends FrameLayout // (This usually happens for most 1:1 conversations) conversationText = messagingGroup.getSenderName(); } - Icon avatarIcon = messagingGroup.getAvatarIcon(); - if (avatarIcon == null) { - avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor); + if (mConversationIcon == null) { + Icon avatarIcon = messagingGroup.getAvatarIcon(); + if (avatarIcon == null) { + avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor); + } + mConversationIcon = avatarIcon; } - mConversationIcon = avatarIcon; - mConversationIconView.setImageIcon(mConversationIcon); break; } } + } + if (mConversationIcon == null) { + mConversationIcon = mLargeIcon; + } + if (mIsOneToOne || mConversationIcon != null) { + mConversationIconView.setVisibility(VISIBLE); + mConversationFacePile.setVisibility(GONE); + mConversationIconView.setImageIcon(mConversationIcon); } else { - if (mLargeIcon != null) { - mConversationIcon = mLargeIcon; - mConversationIconView.setVisibility(VISIBLE); - mConversationFacePile.setVisibility(GONE); - mConversationIconView.setImageIcon(mLargeIcon); - } else { - mConversationIcon = null; - mConversationIconView.setVisibility(GONE); - // This will also inflate it! - mConversationFacePile.setVisibility(VISIBLE); - // rebind the value to the inflated view instead of the stub - mConversationFacePile = findViewById(R.id.conversation_face_pile); - bindFacePile(); - } + mConversationIconView.setVisibility(GONE); + // This will also inflate it! + mConversationFacePile.setVisibility(VISIBLE); + // rebind the value to the inflated view instead of the stub + mConversationFacePile = findViewById(R.id.conversation_face_pile); + bindFacePile(); } if (TextUtils.isEmpty(conversationText)) { conversationText = mIsOneToOne ? mFallbackChatName : mFallbackGroupChatName; @@ -691,9 +768,13 @@ public class ConversationLayout extends FrameLayout // group : mExpandedGroupMessagePadding; + int iconPadding = mIsOneToOne || mIsCollapsed + ? mConversationIconTopPadding + : mConversationIconTopPaddingExpandedGroup; + mConversationIconContainer.setPaddingRelative( mConversationIconContainer.getPaddingStart(), - mConversationIconTopPadding, + iconPadding, mConversationIconContainer.getPaddingEnd(), mConversationIconContainer.getPaddingBottom()); @@ -709,6 +790,11 @@ public class ConversationLayout extends FrameLayout mLargeIcon = largeIcon; } + @RemotableViewMethod + public void setShortcutIcon(Icon shortcutIcon) { + mShortcutIcon = shortcutIcon; + } + /** * Sets the conversation title of this conversation. * @@ -716,7 +802,8 @@ public class ConversationLayout extends FrameLayout */ @RemotableViewMethod public void setConversationTitle(CharSequence conversationTitle) { - mConversationTitle = conversationTitle; + // Remove formatting from the title. + mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null; } public CharSequence getConversationTitle() { @@ -973,6 +1060,9 @@ public class ConversationLayout extends FrameLayout groups.add(currentGroup); if (sender == null) { sender = mUser; + } else { + // Remove all formatting from the sender name + sender = sender.toBuilder().setName(Objects.toString(sender.getName())).build(); } senders.add(sender); currentSenderKey = key; @@ -1170,7 +1260,6 @@ public class ConversationLayout extends FrameLayout } private void updateContentEndPaddings() { - // Let's make sure the conversation header can't run into the expand button when we're // collapsed and update the paddings of the content int headerPaddingEnd; @@ -1215,9 +1304,10 @@ public class ConversationLayout extends FrameLayout if (expandable) { mExpandButtonContainer.setVisibility(VISIBLE); mExpandButtonInnerContainer.setOnClickListener(onClickListener); + mConversationIconContainer.setOnClickListener(onClickListener); } else { - // TODO: handle content paddings to end of layout mExpandButtonContainer.setVisibility(GONE); + mConversationIconContainer.setOnClickListener(null); } updateContentEndPaddings(); } diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 03a7b3da6251af9707cabeee7e8eabf6b8d422c0..93690cdfc811d7ee64734291d932634b47b1573a 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -1430,6 +1430,32 @@ public class LockPatternUtils { == StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; } + private static class WrappedCallback extends ICheckCredentialProgressCallback.Stub { + + private Handler mHandler; + private CheckCredentialProgressCallback mCallback; + + WrappedCallback(Handler handler, CheckCredentialProgressCallback callback) { + mHandler = handler; + mCallback = callback; + } + + @Override + public void onCredentialVerified() throws RemoteException { + if (mHandler == null) { + Log.e(TAG, "Handler is null during callback"); + } + // Kill reference immediately to allow early GC at client side independent of + // when system_server decides to lose its reference to the + // ICheckCredentialProgressCallback binder object. + mHandler.post(() -> { + mCallback.onEarlyMatched(); + mCallback = null; + }); + mHandler = null; + } + } + private ICheckCredentialProgressCallback wrapCallback( final CheckCredentialProgressCallback callback) { if (callback == null) { @@ -1439,13 +1465,7 @@ public class LockPatternUtils { throw new IllegalStateException("Must construct LockPatternUtils on a looper thread" + " to use progress callbacks."); } - return new ICheckCredentialProgressCallback.Stub() { - - @Override - public void onCredentialVerified() throws RemoteException { - mHandler.post(callback::onEarlyMatched); - } - }; + return new WrappedCallback(mHandler, callback); } } diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 53272f7eebf95379ea59e859ab04103f765a1565..f312d1d4f25db353bbd32e7254482b213c00e6a2 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -42,6 +42,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RemoteViews; +import android.widget.TextView; import com.android.internal.R; @@ -109,6 +110,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou private ViewGroup mMessagingIconContainer; private int mConversationContentStart; private int mNonConversationMarginEnd; + private int mNotificationTextMarginTop; public MessagingGroup(@NonNull Context context) { super(context); @@ -148,6 +150,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou R.dimen.conversation_content_start); mNonConversationMarginEnd = getResources().getDimensionPixelSize( R.dimen.messaging_layout_margin_end); + mNotificationTextMarginTop = getResources().getDimensionPixelSize( + R.dimen.notification_text_margin_top); } public void updateClipRect() { @@ -612,7 +616,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return 0; } - public View getSenderView() { + public TextView getSenderView() { return mSenderView; } @@ -664,10 +668,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public void setSingleLine(boolean singleLine) { if (singleLine != mSingleLine) { mSingleLine = singleLine; + MarginLayoutParams p = (MarginLayoutParams) mMessageContainer.getLayoutParams(); + p.topMargin = singleLine ? 0 : mNotificationTextMarginTop; + mMessageContainer.setLayoutParams(p); mContentContainer.setOrientation( singleLine ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); MarginLayoutParams layoutParams = (MarginLayoutParams) mSenderView.getLayoutParams(); layoutParams.setMarginEnd(singleLine ? mSenderTextPaddingSingleLine : 0); + mSenderView.setSingleLine(singleLine); updateMaxDisplayedLines(); updateClipRect(); updateSenderVisibility(); diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index a162e4e10c71f3157caa3d310a5b4746b7c6fe4f..27cd6e13d86c00c26ce9ef280e25515cda11e13a 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -124,7 +124,6 @@ public class MessagingLayout extends FrameLayout protected void onFinishInflate() { super.onFinishInflate(); mMessagingLinearLayout = findViewById(R.id.notification_messaging); - mMessagingLinearLayout.setMessagingLayout(this); // We still want to clip, but only on the top, since views can temporarily out of bounds // during transitions. DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index ac04862d9a7d7f1680bfdff72c9a3bf7158ba760..7cfd46c880fce665175da7b9d4345c688b23a4ba 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -24,6 +24,7 @@ import android.util.AttributeSet; import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.widget.RemoteViews; import com.android.internal.R; @@ -43,8 +44,6 @@ public class MessagingLinearLayout extends ViewGroup { private int mMaxDisplayedLines = Integer.MAX_VALUE; - private IMessagingLayout mMessagingLayout; - public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -292,12 +291,40 @@ public class MessagingLinearLayout extends ViewGroup { mMaxDisplayedLines = numberLines; } - public void setMessagingLayout(IMessagingLayout layout) { - mMessagingLayout = layout; + public IMessagingLayout getMessagingLayout() { + View view = this; + while (true) { + ViewParent p = view.getParent(); + if (p instanceof View) { + view = (View) p; + if (view instanceof IMessagingLayout) { + return (IMessagingLayout) view; + } + } else { + return null; + } + } } - public IMessagingLayout getMessagingLayout() { - return mMessagingLayout; + @Override + public int getBaseline() { + // When placed in a horizontal linear layout (as is the case in a single-line MessageGroup), + // align with the last visible child (which is the one that will be displayed in the single- + // line group. + int childCount = getChildCount(); + for (int i = childCount - 1; i >= 0; i--) { + final View child = getChildAt(i); + if (isGone(child)) { + continue; + } + final int childBaseline = child.getBaseline(); + if (childBaseline == -1) { + return -1; + } + MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + return lp.topMargin + childBaseline; + } + return super.getBaseline(); } public interface MessagingChild { diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 21ca948fa89cbc1ffd50b314a04c764bea8f4928..ea390cd71e3186d1cd67e10a535e29ae26ccd72a 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -24,6 +24,7 @@ import android.content.ComponentName; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; import android.os.Build; +import android.os.CarrierAssociatedAppEntry; import android.os.Environment; import android.os.FileUtils; import android.os.Process; @@ -198,8 +199,8 @@ public class SystemConfig { // These are the packages of carrier-associated apps which should be disabled until used until // a SIM is inserted which grants carrier privileges to that carrier app. - final ArrayMap> mDisabledUntilUsedPreinstalledCarrierAssociatedApps = - new ArrayMap<>(); + final ArrayMap> + mDisabledUntilUsedPreinstalledCarrierAssociatedApps = new ArrayMap<>(); final ArrayMap> mPrivAppPermissions = new ArrayMap<>(); final ArrayMap> mPrivAppDenyPermissions = new ArrayMap<>(); @@ -331,7 +332,8 @@ public class SystemConfig { return mDisabledUntilUsedPreinstalledCarrierApps; } - public ArrayMap> getDisabledUntilUsedPreinstalledCarrierAssociatedApps() { + public ArrayMap> + getDisabledUntilUsedPreinstalledCarrierAssociatedApps() { return mDisabledUntilUsedPreinstalledCarrierAssociatedApps; } @@ -954,7 +956,23 @@ public class SystemConfig { + "> without package or carrierAppPackage in " + permFile + " at " + parser.getPositionDescription()); } else { - List associatedPkgs = + // APKs added to system images via OTA should specify the addedInSdk + // attribute, otherwise they may be enabled-by-default in too many + // cases. See CarrierAppUtils for more info. + int addedInSdk = CarrierAssociatedAppEntry.SDK_UNSPECIFIED; + String addedInSdkStr = parser.getAttributeValue(null, "addedInSdk"); + if (!TextUtils.isEmpty(addedInSdkStr)) { + try { + addedInSdk = Integer.parseInt(addedInSdkStr); + } catch (NumberFormatException e) { + Slog.w(TAG, "<" + name + "> addedInSdk not an integer in " + + permFile + " at " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + break; + } + } + List associatedPkgs = mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get( carrierPkgname); if (associatedPkgs == null) { @@ -962,7 +980,8 @@ public class SystemConfig { mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put( carrierPkgname, associatedPkgs); } - associatedPkgs.add(pkgname); + associatedPkgs.add( + new CarrierAssociatedAppEntry(pkgname, addedInSdk)); } } else { logNotAllowedInPartition(name, permFile, parser); @@ -1197,6 +1216,10 @@ public class SystemConfig { addFeature(PackageManager.FEATURE_APP_ENUMERATION, 0); } + if (Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.Q) { + addFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0); + } + for (String featureName : mUnavailableFeatures) { removeFeature(featureName); } diff --git a/core/jni/android_media_AudioEffectDescriptor.cpp b/core/jni/android_media_AudioEffectDescriptor.cpp index 37d8114052b8ad4bf2072889d715821115086a0f..1435e879053cdcfd863266dde174c8e887617401 100644 --- a/core/jni/android_media_AudioEffectDescriptor.cpp +++ b/core/jni/android_media_AudioEffectDescriptor.cpp @@ -102,9 +102,9 @@ void convertAudioEffectDescriptorVectorFromNative(JNIEnv *env, jobjectArray *jDe *jDescriptors = env->NewObjectArray(actualSize, audioEffectDescriptorClass(), NULL); for (size_t i = 0; i < actualSize; i++) { - env->SetObjectArrayElement(*jDescriptors, - i, - env->GetObjectArrayElement(temp, i)); + jobject jdesc = env->GetObjectArrayElement(temp, i); + env->SetObjectArrayElement(*jDescriptors, i, jdesc); + env->DeleteLocalRef(jdesc); } env->DeleteLocalRef(temp); } diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h index a3c455bfc111d0f171b0123ad811034b30443212..b1b39f3e36ffe1f40a24bfd176b46a8759fc99f0 100644 --- a/core/jni/android_media_AudioFormat.h +++ b/core/jni/android_media_AudioFormat.h @@ -47,6 +47,7 @@ #define CHANNEL_INVALID 0 #define CHANNEL_OUT_DEFAULT 1 +#define CHANNEL_IN_DEFAULT 1 static inline audio_format_t audioFormatToNative(int audioFormat) { @@ -196,12 +197,22 @@ static inline int outChannelMaskFromNative(audio_channel_mask_t nativeMask) static inline audio_channel_mask_t inChannelMaskToNative(int channelMask) { - return (audio_channel_mask_t)channelMask; + switch (channelMask) { + case CHANNEL_IN_DEFAULT: + return AUDIO_CHANNEL_NONE; + default: + return (audio_channel_mask_t)channelMask; + } } static inline int inChannelMaskFromNative(audio_channel_mask_t nativeMask) { - return (int)nativeMask; + switch (nativeMask) { + case AUDIO_CHANNEL_NONE: + return CHANNEL_IN_DEFAULT; + default: + return (int)nativeMask; + } } #endif // ANDROID_MEDIA_AUDIOFORMAT_H diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp index 03b9793ccba8be473cf255fb9ae450738a6c3493..d4805acb06d08b7e5c3c65a4bbd79b9b36910729 100644 --- a/core/jni/android_net_NetUtils.cpp +++ b/core/jni/android_net_NetUtils.cpp @@ -226,6 +226,11 @@ static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) { class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass); } +static void android_net_utils_setAllowNetworkingForProcess(JNIEnv *env, jobject thiz, + jboolean hasConnectivity) { + setAllowNetworkingForProcess(hasConnectivity == JNI_TRUE); +} + static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) { if (javaFd == NULL) { jniThrowNullPointerException(env, NULL); @@ -266,6 +271,7 @@ static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, j /* * JNI registration. */ +// clang-format off static const JNINativeMethod gNetworkUtilMethods[] = { /* name, signature, funcPtr */ { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork }, @@ -282,7 +288,9 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult }, { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel }, { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork }, + { "setAllowNetworkingForProcess", "(Z)V", (void *)android_net_utils_setAllowNetworkingForProcess }, }; +// clang-format on int register_android_net_NetworkUtils(JNIEnv* env) { diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index e553a786da96bfab242f7a2769d5b16281e06af2..ae36f8a7b30bf27025ab92599f8abae5d71a91bb 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -395,6 +395,16 @@ static void nativeSetEarlyWakeup(JNIEnv* env, jclass clazz, jlong transactionObj transaction->setEarlyWakeup(); } +static void nativeSetEarlyWakeupStart(JNIEnv* env, jclass clazz, jlong transactionObj) { + auto transaction = reinterpret_cast(transactionObj); + transaction->setExplicitEarlyWakeupStart(); +} + +static void nativeSetEarlyWakeupEnd(JNIEnv* env, jclass clazz, jlong transactionObj) { + auto transaction = reinterpret_cast(transactionObj); + transaction->setExplicitEarlyWakeupEnd(); +} + static void nativeSetLayer(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jint zorder) { auto transaction = reinterpret_cast(transactionObj); @@ -1501,6 +1511,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetAnimationTransaction }, {"nativeSetEarlyWakeup", "(J)V", (void*)nativeSetEarlyWakeup }, + {"nativeSetEarlyWakeupStart", "(J)V", + (void*)nativeSetEarlyWakeupStart }, + {"nativeSetEarlyWakeupEnd", "(J)V", + (void*)nativeSetEarlyWakeupEnd }, {"nativeSetLayer", "(JJI)V", (void*)nativeSetLayer }, {"nativeSetRelativeLayer", "(JJJI)V", diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index fc2005a31696cc3f4a3368a992f5288f03816bab..9eede83e21e52b265e80460954a1223f9915dcaf 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -526,8 +526,16 @@ static void UnsetChldSignalHandler() { // Calls POSIX setgroups() using the int[] object as an argument. // A nullptr argument is tolerated. -static void SetGids(JNIEnv* env, jintArray managed_gids, fail_fn_t fail_fn) { +static void SetGids(JNIEnv* env, jintArray managed_gids, jboolean is_child_zygote, + fail_fn_t fail_fn) { if (managed_gids == nullptr) { + if (is_child_zygote) { + // For child zygotes like webview and app zygote, we want to clear out + // any supplemental groups the parent zygote had. + if (setgroups(0, NULL) == -1) { + fail_fn(CREATE_ERROR("Failed to remove supplementary groups for child zygote")); + } + } return; } @@ -1351,7 +1359,13 @@ static void isolateAppData(JNIEnv* env, const std::vector& merged_d } closedir(dir); - bool legacySymlinkCreated = false; + // Prepare default dirs for user 0 as user 0 always exists. + int result = symlink("/data/data", "/data/user/0"); + if (result != 0) { + fail_fn(CREATE_ERROR("Failed to create symlink /data/user/0 %s", strerror(errno))); + } + PrepareDirIfNotPresent("/data/user_de/0", DEFAULT_DATA_DIR_PERMISSION, + AID_ROOT, AID_ROOT, fail_fn); for (int i = 0; i < size; i += 3) { std::string const & packageName = merged_data_info_list[i]; @@ -1392,17 +1406,8 @@ static void isolateAppData(JNIEnv* env, const std::vector& merged_d char internalDeUserPath[PATH_MAX]; snprintf(internalCeUserPath, PATH_MAX, "/data/user/%d", userId); snprintf(internalDeUserPath, PATH_MAX, "/data/user_de/%d", userId); - // If it's user 0, create a symlink /data/user/0 -> /data/data, - // otherwise create /data/user/$USER + // If it's not user 0, create /data/user/$USER. if (userId == 0) { - if (!legacySymlinkCreated) { - legacySymlinkCreated = true; - int result = symlink(internalLegacyCePath, internalCeUserPath); - if (result != 0) { - fail_fn(CREATE_ERROR("Failed to create symlink %s %s", internalCeUserPath, - strerror(errno))); - } - } actualCePath = internalLegacyCePath; } else { PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION, @@ -1579,10 +1584,6 @@ static void BindMountStorageDirs(JNIEnv* env, jobjectArray pkg_data_info_list, // Fuse is ready, so we can start using fuse path. int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0; - if (size == 0) { - fail_fn(CREATE_ERROR("Data package list cannot be empty")); - } - // Create tmpfs on Android/obb and Android/data so these 2 dirs won't enter fuse anymore. std::string androidObbDir = StringPrintf("/storage/emulated/%d/Android/obb", user_id); MountAppDataTmpFs(androidObbDir, fail_fn); @@ -1665,7 +1666,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } - SetGids(env, gids, fail_fn); + SetGids(env, gids, is_child_zygote, fail_fn); SetRLimits(env, rlimits, fail_fn); if (need_pre_initialize_native_bridge) { @@ -1736,6 +1737,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE; } android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level)); + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime. + runtime_flags &= ~RuntimeFlags::MEMORY_TAG_LEVEL_MASK; bool forceEnableGwpAsan = false; switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) { @@ -1748,6 +1751,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY: android_mallopt(M_INITIALIZE_GWP_ASAN, &forceEnableGwpAsan, sizeof(forceEnableGwpAsan)); } + // Now that we've used the flag, clear it so that we don't pass unknown flags to the ART runtime. + runtime_flags &= ~RuntimeFlags::GWP_ASAN_LEVEL_MASK; if (NeedsNoRandomizeWorkaround()) { // Work around ARM kernel ASLR lossage (http://b/5817320). diff --git a/core/proto/android/app/enums.proto b/core/proto/android/app/enums.proto index 563ef145b79c87331ea6b62ccf923e20ba2a6468..bd5cb62f7fdecb206585d51f297942835c6e3294 100644 --- a/core/proto/android/app/enums.proto +++ b/core/proto/android/app/enums.proto @@ -206,4 +206,5 @@ enum AppOpEnum { APP_OP_DEPRECATED_1 = 96 [deprecated = true]; APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED = 97; APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER = 98; + APP_OP_NO_ISOLATED_STORAGE = 99; } diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index 997829eacf96c66df8c3e36b1b4bd05fe76976ec..69b32c264d3df3c9ccebed3a9fb4f65e02c28f8b 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2678,4 +2678,9 @@ enum PageId { // CATEGORY: SETTINGS // OS: R DEVICE_CONTROLS_SETTINGS = 1844; + + // OPEN: Settings > Sound > Media + // CATEGORY: SETTINGS + // OS: R + MEDIA_CONTROLS_SETTINGS = 1845; } diff --git a/core/proto/android/server/blobstoremanagerservice.proto b/core/proto/android/server/blobstoremanagerservice.proto new file mode 100644 index 0000000000000000000000000000000000000000..583b646eb9c7884def2c99171841b6bd82659744 --- /dev/null +++ b/core/proto/android/server/blobstoremanagerservice.proto @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; +package com.android.server.blob; + +option java_multiple_files = true; + +// The nested messages are used for statsd logging and should be kept in sync with the messages +// of the same name in frameworks/base/cmds/statsd/src/atoms.proto +message BlobStatsEventProto { + // Blob Committer stats + // Keep in sync between: + // frameworks/base/core/proto/android/server/blobstoremanagerservice.proto + // frameworks/base/cmds/statsd/src/atoms.proto + message BlobCommitterProto { + // Committer app's uid + optional int32 uid = 1; + + // Unix epoch timestamp of the commit in milliseconds + optional int64 commit_timestamp_millis = 2; + + // Flags of what access types the committer has set for the Blob + optional int32 access_mode = 3; + + // Number of packages that have been whitelisted for ACCESS_TYPE_WHITELIST + optional int32 num_whitelisted_package = 4; + } + + // Blob Leasee stats + // Keep in sync between: + // frameworks/base/core/proto/android/server/blobstoremanagerservice.proto + // frameworks/base/cmds/statsd/src/atoms.proto + message BlobLeaseeProto { + // Leasee app's uid + optional int32 uid = 1; + + // Unix epoch timestamp for lease expiration in milliseconds + optional int64 lease_expiry_timestamp_millis = 2; + } + + // List of Blob Committers + // Keep in sync between: + // frameworks/base/core/proto/android/server/blobstoremanagerservice.proto + // frameworks/base/cmds/statsd/src/atoms.proto + message BlobCommitterListProto { + repeated BlobCommitterProto committer = 1; + } + + // List of Blob Leasees + // Keep in sync between: + // frameworks/base/core/proto/android/server/blobstoremanagerservice.proto + // frameworks/base/cmds/statsd/src/atoms.proto + message BlobLeaseeListProto { + repeated BlobLeaseeProto leasee = 1; + } +} \ No newline at end of file diff --git a/core/proto/android/server/connectivity/data_stall_event.proto b/core/proto/android/server/connectivity/data_stall_event.proto index 23fcf6ebc2cc0b819be006861d0ac1088b70b1ca..787074ba494eb1fbda0e82ba3ddeed4eea1c1b69 100644 --- a/core/proto/android/server/connectivity/data_stall_event.proto +++ b/core/proto/android/server/connectivity/data_stall_event.proto @@ -32,6 +32,7 @@ enum ApBand { AP_BAND_UNKNOWN = 0; AP_BAND_2GHZ = 1; AP_BAND_5GHZ = 2; + AP_BAND_6GHZ = 3; } // Refer to definition in TelephonyManager.java. diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index ec99684bf6366ccaa2f3bc0851b184589c9cb5d3..f2f20e3ac12ebf646821b41c586b40a408a0f595 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -236,6 +236,8 @@ message ConstantsProto { optional int64 api_quota_schedule_window_ms = 33; // Whether or not to throw an exception when an app hits its schedule quota limit. optional bool api_quota_schedule_throw_exception = 34; + // Whether or not to return a failure result when an app hits its schedule quota limit. + optional bool api_quota_schedule_return_failure_result = 35; message QuotaController { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -335,7 +337,7 @@ message ConstantsProto { // In this time after screen turns on, we increase job concurrency. optional int32 screen_off_job_concurrency_increase_delay_ms = 28; - // Next tag: 35 + // Next tag: 36 } // Next tag: 4 diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto index ecb4193a2c6c1eb5c2fc39b522bb1955db748ad1..8e4006aa6861498ce70a069b31e1b19ad9133b2e 100644 --- a/core/proto/android/service/notification.proto +++ b/core/proto/android/service/notification.proto @@ -274,4 +274,74 @@ message PackageRemoteViewInfoProto { // Next Tag: 2 message NotificationRemoteViewsProto { repeated PackageRemoteViewInfoProto package_remote_view_info = 1; -} \ No newline at end of file +} + +/** + * Atom that represents an item in the list of Do Not Disturb rules, pulled from + * NotificationManagerService.java. + */ +message DNDModeProto { + enum Mode { + ROOT_CONFIG = -1; // Used to distinguish the config (one per user) from the rules. + ZEN_MODE_OFF = 0; + ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; + ZEN_MODE_NO_INTERRUPTIONS = 2; + ZEN_MODE_ALARMS = 3; + } + optional int32 user = 1; // Android user ID (0, 1, 10, ...) + optional bool enabled = 2; // true for ROOT_CONFIG if a manualRule is enabled + optional bool channels_bypassing = 3; // only valid for ROOT_CONFIG + optional Mode zen_mode = 4; + // id is one of the system default rule IDs, or empty + // May also be "MANUAL_RULE" to indicate app-activation of the manual rule. + optional string id = 5; + optional int32 uid = 6; // currently only SYSTEM_UID or 0 for other + optional DNDPolicyProto policy = 7; +} + +/** + * Atom that represents a Do Not Disturb policy, an optional detail proto for DNDModeProto. + */ +message DNDPolicyProto { + enum State { + STATE_UNSET = 0; + STATE_ALLOW = 1; + STATE_DISALLOW = 2; + } + optional State calls = 1; + optional State repeat_callers = 2; + optional State messages = 3; + optional State conversations = 4; + optional State reminders = 5; + optional State events = 6; + optional State alarms = 7; + optional State media = 8; + optional State system = 9; + optional State fullscreen = 10; + optional State lights = 11; + optional State peek = 12; + optional State status_bar = 13; + optional State badge = 14; + optional State ambient = 15; + optional State notification_list = 16; + + enum PeopleType { + PEOPLE_UNSET = 0; + PEOPLE_ANYONE = 1; + PEOPLE_CONTACTS = 2; + PEOPLE_STARRED = 3; + PEOPLE_NONE = 4; + } + + optional PeopleType allow_calls_from = 17; + optional PeopleType allow_messages_from = 18; + + enum ConversationType { + CONV_UNSET = 0; + CONV_ANYONE = 1; + CONV_IMPORTANT = 2; + CONV_NONE = 3; + } + + optional ConversationType allow_conversations_from = 19; +} diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto index dd830a85edc9407cead3ded89f656054f8324456..7a4c0706e1190a9173f07a62375b7f21e96f390e 100644 --- a/core/proto/android/service/procstats.proto +++ b/core/proto/android/service/procstats.proto @@ -179,13 +179,16 @@ message ProcessStatsProto { repeated ProcessStatsAssociationProto assocs = 7; } -// Next Tag: 5 +// Next Tag: 6 message ProcessStatsAssociationProto { // Procss Name of the associated process/package optional string assoc_process_name = 1; // Package Name of the associated process/package - optional string assoc_package_name = 2; + optional string assoc_package_name = 2 [deprecated = true]; + + // UID of the associated process/package + optional int32 assoc_uid = 5; // Total count of the times this association appeared. optional int32 total_count = 3; diff --git a/core/proto/android/stats/connectivity/Android.bp b/core/proto/android/stats/connectivity/Android.bp index 5d642d3845fee472b3b3e651f31a4a97dcfe0a37..5e6ac3cd3ca1ab61d801693757f85690050133e5 100644 --- a/core/proto/android/stats/connectivity/Android.bp +++ b/core/proto/android/stats/connectivity/Android.bp @@ -13,12 +13,26 @@ // limitations under the License. java_library_static { - name: "networkstackprotosnano", + name: "networkstackprotos", proto: { - type: "nano", + type: "lite", }, srcs: [ "network_stack.proto", ], + sdk_version: "system_29", +} + +java_library_static { + name: "tetheringprotos", + proto: { + type: "lite", + }, + srcs: [ + "tethering.proto", + ], + apex_available: [ + "com.android.tethering", + ], sdk_version: "system_current", } diff --git a/core/proto/android/stats/connectivity/network_stack.proto b/core/proto/android/stats/connectivity/network_stack.proto index 7d9aa1c6eb230ab293eb7cea7af81a1c6a3619b3..e9726d7ce195426cec8723d10ed2575dd46d5999 100644 --- a/core/proto/android/stats/connectivity/network_stack.proto +++ b/core/proto/android/stats/connectivity/network_stack.proto @@ -20,6 +20,160 @@ package android.stats.connectivity; option java_multiple_files = true; option java_outer_classname = "NetworkStackProto"; +enum DhcpRenewResult { + RR_UNKNOWN = 0; + RR_SUCCESS = 1; + RR_ERROR_NAK = 2; + RR_ERROR_IP_MISMATCH = 3; + RR_ERROR_IP_EXPIRE = 4; +} + +enum DisconnectCode { + DC_NONE = 0; + DC_NORMAL_TERMINATION = 1; + DC_PROVISIONING_FAIL = 2; + DC_ERROR_STARTING_IPV4 = 4; + DC_ERROR_STARTING_IPV6 = 5; + DC_ERROR_STARTING_IPREACHABILITYMONITOR = 6; + DC_INVALID_PROVISIONING = 7; + DC_INTERFACE_NOT_FOUND = 8; + DC_PROVISIONING_TIMEOUT = 9; +} + +enum TransportType { + TT_UNKNOWN = 0; + // Indicates this network uses a Cellular transport + TT_CELLULAR = 1; + // Indicates this network uses a Wi-Fi transport + TT_WIFI = 2; + // Indicates this network uses a Bluetooth transport + TT_BLUETOOTH = 3; + // Indicates this network uses an Ethernet transport + TT_ETHERNET = 4; + // Indicates this network uses a Wi-Fi Aware transport + TT_WIFI_AWARE = 5; + // Indicates this network uses a LoWPAN transport + TT_LOWPAN = 6; + // Indicates this network uses a Cellular+VPN transport + TT_CELLULAR_VPN = 7; + // Indicates this network uses a Wi-Fi+VPN transport + TT_WIFI_VPN = 8; + // Indicates this network uses a Bluetooth+VPN transport + TT_BLUETOOTH_VPN = 9; + // Indicates this network uses an Ethernet+VPN transport + TT_ETHERNET_VPN = 10; + // Indicates this network uses a Wi-Fi+Cellular+VPN transport + TT_WIFI_CELLULAR_VPN = 11; + // Indicates this network uses for test only + TT_TEST = 12; +} + +enum DhcpFeature { + DF_UNKNOWN = 0; + // DHCP INIT-REBOOT state + DF_INITREBOOT = 1; + // DHCP rapid commit option + DF_RAPIDCOMMIT = 2; + // Duplicate address detection + DF_DAD = 3; + // Fast initial Link setup + DF_FILS = 4; +} + +enum HostnameTransResult { + HTR_UNKNOWN = 0; + HTR_SUCCESS = 1; + HTR_FAILURE = 2; + HTR_DISABLE = 3; +} + +enum ProbeResult { + PR_UNKNOWN = 0; + PR_SUCCESS = 1; + PR_FAILURE = 2; + PR_PORTAL = 3; + // DNS query for the probe host returned a private IP address + PR_PRIVATE_IP_DNS = 4; +} + +enum ValidationResult { + VR_UNKNOWN = 0; + VR_SUCCESS = 1; + VR_FAILURE = 2; + VR_PORTAL = 3; + VR_PARTIAL = 4; +} + +enum ProbeType { + PT_UNKNOWN = 0; + PT_DNS = 1; + PT_HTTP = 2; + PT_HTTPS = 3; + PT_PAC = 4; + PT_FALLBACK = 5; + PT_PRIVDNS = 6; + PT_CAPPORT_API = 7; +} + +// The Dhcp error code is defined in android.net.metrics.DhcpErrorEvent +enum DhcpErrorCode { + ET_UNKNOWN = 0; + ET_L2_ERROR = 1; + ET_L3_ERROR = 2; + ET_L4_ERROR = 3; + ET_DHCP_ERROR = 4; + ET_MISC_ERROR = 5; + /* Reserve for error type + // ET_L2_ERROR_TYPE = ET_L2_ERROR << 8; + ET_L2_ERROR_TYPE = 256; + // ET_L3_ERROR_TYPE = ET_L3_ERROR << 8; + ET_L3_ERROR_TYPE = 512; + // ET_L4_ERROR_TYPE = ET_L4_ERROR << 8; + ET_L4_ERROR_TYPE = 768; + // ET_DHCP_ERROR_TYPE = ET_DHCP_ERROR << 8; + ET_DHCP_ERROR_TYPE = 1024; + // ET_MISC_ERROR_TYPE = ET_MISC_ERROR << 8; + ET_MISC_ERROR_TYPE = 1280; + */ + // ET_L2_TOO_SHORT = (ET_L2_ERROR_TYPE | 0x1) << 16; + ET_L2_TOO_SHORT = 16842752; + // ET_L2_WRONG_ETH_TYPE = (ET_L2_ERROR_TYPE | 0x2) << 16; + ET_L2_WRONG_ETH_TYPE = 16908288; + // ET_L3_TOO_SHORT = (ET_L3_ERROR_TYPE | 0x1) << 16; + ET_L3_TOO_SHORT = 33619968; + // ET_L3_NOT_IPV4 = (ET_L3_ERROR_TYPE | 0x2) << 16; + ET_L3_NOT_IPV4 = 33685504; + // ET_L3_INVALID_IP = (ET_L3_ERROR_TYPE | 0x3) << 16; + ET_L3_INVALID_IP = 33751040; + // ET_L4_NOT_UDP = (ET_L4_ERROR_TYPE | 0x1) << 16; + ET_L4_NOT_UDP = 50397184; + // ET_L4_WRONG_PORT = (ET_L4_ERROR_TYPE | 0x2) << 16; + ET_L4_WRONG_PORT = 50462720; + // ET_BOOTP_TOO_SHORT = (ET_DHCP_ERROR_TYPE | 0x1) << 16; + ET_BOOTP_TOO_SHORT = 67174400; + // ET_DHCP_BAD_MAGIC_COOKIE = (ET_DHCP_ERROR_TYPE | 0x2) << 16; + ET_DHCP_BAD_MAGIC_COOKIE = 67239936; + // ET_DHCP_INVALID_OPTION_LENGTH = (ET_DHCP_ERROR_TYPE | 0x3) << 16; + ET_DHCP_INVALID_OPTION_LENGTH = 67305472; + // ET_DHCP_NO_MSG_TYPE = (ET_DHCP_ERROR_TYPE | 0x4) << 16; + ET_DHCP_NO_MSG_TYPE = 67371008; + // ET_DHCP_UNKNOWN_MSG_TYPE = (ET_DHCP_ERROR_TYPE | 0x5) << 16; + ET_DHCP_UNKNOWN_MSG_TYPE = 67436544; + // ET_DHCP_NO_COOKIE = (ET_DHCP_ERROR_TYPE | 0x6) << 16; + ET_DHCP_NO_COOKIE = 67502080; + // ET_BUFFER_UNDERFLOW = (ET_MISC_ERROR_TYPE | 0x1) << 16; + ET_BUFFER_UNDERFLOW = 83951616; + // ET_RECEIVE_ERROR = (ET_MISC_ERROR_TYPE | 0x2) << 16; + ET_RECEIVE_ERROR = 84017152; + // ET_PARSING_ERROR = (ET_MISC_ERROR_TYPE | 0x3) << 16; + ET_PARSING_ERROR = 84082688; +} + +enum NetworkQuirkEvent { + QE_UNKNOWN = 0; + QE_IPV6_PROVISIONING_ROUTER_LOST = 1; +} + message NetworkStackEventData { } diff --git a/core/proto/android/stats/connectivity/tethering.proto b/core/proto/android/stats/connectivity/tethering.proto new file mode 100644 index 0000000000000000000000000000000000000000..13f0b8c44fb52ba2ec90bed830ebd2c847629871 --- /dev/null +++ b/core/proto/android/stats/connectivity/tethering.proto @@ -0,0 +1,97 @@ +/* + * 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. + */ +syntax = "proto2"; +package android.stats.connectivity; +option java_multiple_files = true; +option java_outer_classname = "TetheringProto"; + +enum ErrorCode { + EC_NO_ERROR = 0; + EC_UNKNOWN_IFACE = 1; + EC_SERVICE_UNAVAIL = 2; + EC_UNSUPPORTED = 3; + EC_UNAVAIL_IFACE = 4; + EC_INTERNAL_ERROR = 5; + EC_TETHER_IFACE_ERROR = 6; + EC_UNTETHER_IFACE_ERROR = 7; + EC_ENABLE_FORWARDING_ERROR = 8; + EC_DISABLE_FORWARDING_ERROR = 9; + EC_IFACE_CFG_ERROR = 10; + EC_PROVISIONING_FAILED = 11; + EC_DHCPSERVER_ERROR = 12; + EC_ENTITLEMENT_UNKNOWN = 13; + EC_NO_CHANGE_TETHERING_PERMISSION = 14; + EC_NO_ACCESS_TETHERING_PERMISSION = 15; + EC_UNKNOWN_TYPE = 16; +} + +enum DownstreamType { + // Unspecific tethering type. + DS_UNSPECIFIED = 0; + // Wifi tethering type. + DS_TETHERING_WIFI = 1; + // USB tethering type. + DS_TETHERING_USB = 2; + // Bluetooth tethering type. + DS_TETHERING_BLUETOOTH = 3; + // Wifi P2p tethering type. + DS_TETHERING_WIFI_P2P = 4; + // NCM (Network Control Model) local tethering type. + DS_TETHERING_NCM = 5; + // Ethernet tethering type. + DS_TETHERING_ETHERNET = 6; +} + +enum UpstreamType { + UT_UNKNOWN = 0; + // Indicates upstream using a Cellular transport. + UT_CELLULAR = 1; + // Indicates upstream using a Wi-Fi transport. + UT_WIFI = 2; + // Indicates upstream using a Bluetooth transport. + UT_BLUETOOTH = 3; + // Indicates upstream using an Ethernet transport. + UT_ETHERNET = 4; + // Indicates upstream using a Wi-Fi Aware transport. + UT_WIFI_AWARE = 5; + // Indicates upstream using a LoWPAN transport. + UT_LOWPAN = 6; + // Indicates upstream using a Cellular+VPN transport. + UT_CELLULAR_VPN = 7; + // Indicates upstream using a Wi-Fi+VPN transport. + UT_WIFI_VPN = 8; + // Indicates upstream using a Bluetooth+VPN transport. + UT_BLUETOOTH_VPN = 9; + // Indicates upstream using an Ethernet+VPN transport. + UT_ETHERNET_VPN = 10; + // Indicates upstream using a Wi-Fi+Cellular+VPN transport. + UT_WIFI_CELLULAR_VPN = 11; + // Indicates upstream using for test only. + UT_TEST = 12; + // Indicates upstream using DUN capability + Cellular transport. + UT_DUN_CELLULAR = 13; +} + +enum UserType { + // Unknown. + USER_UNKNOWN = 0; + // Settings. + USER_SETTINGS = 1; + // System UI. + USER_SYSTEMUI = 2; + // Google mobile service. + USER_GMS = 3; +} diff --git a/core/proto/android/stats/launcher/launcher.proto b/core/proto/android/stats/launcher/launcher.proto index dbd0e038c40c1116b907b1718de3e31e21230daa..fc177d57b19367727da18ccd9c7f8cca4151f6d4 100644 --- a/core/proto/android/stats/launcher/launcher.proto +++ b/core/proto/android/stats/launcher/launcher.proto @@ -32,10 +32,12 @@ enum LauncherAction { } enum LauncherState { - BACKGROUND = 0; - HOME = 1; - OVERVIEW = 2; - ALLAPPS = 3; + LAUNCHER_STATE_UNSPECIFIED = 0; + BACKGROUND = 1; + HOME = 2; + OVERVIEW = 3; + ALLAPPS = 4; + UNCHANGED = 5; } message LauncherTarget { diff --git a/core/proto/android/stats/mediametrics/mediametrics.proto b/core/proto/android/stats/mediametrics/mediametrics.proto index e1af9622adb326804832d6dc966abcbabca1f248..9f0ff591a5063e57a75b618b416458d351ef0b1e 100644 --- a/core/proto/android/stats/mediametrics/mediametrics.proto +++ b/core/proto/android/stats/mediametrics/mediametrics.proto @@ -131,7 +131,7 @@ message AudioTrackData { * Logged from: * frameworks/av/media/libstagefright/MediaCodec.cpp * frameworks/av/services/mediaanalytics/statsd_codec.cpp - * Next Tag: 21 + * Next Tag: 26 */ message CodecData { optional string codec = 1; @@ -156,6 +156,9 @@ message CodecData { optional int64 latency_unknown = 20; optional int32 queue_input_buffer_error = 21; optional int32 queue_secure_input_buffer_error = 22; + optional string bitrate_mode = 23; + optional int32 bitrate = 24; + optional int64 lifetime_millis = 25; } /** diff --git a/core/proto/android/stats/sysui/notification_enums.proto b/core/proto/android/stats/sysui/notification_enums.proto index 09837022e50d31d03b3272c624399101ec3e5488..30bdecae07d142f2fcfc697cf1dcfab8e81c9cad 100644 --- a/core/proto/android/stats/sysui/notification_enums.proto +++ b/core/proto/android/stats/sysui/notification_enums.proto @@ -26,4 +26,5 @@ enum NotificationImportance { // Constants from NotificationManager.java IMPORTANCE_LOW = 2; // Shows in shade, maybe status bar, no buzz/beep. IMPORTANCE_DEFAULT = 3; // Shows everywhere, makes noise, no heads-up. IMPORTANCE_HIGH = 4; // Shows everywhere, makes noise, heads-up, may full-screen. + IMPORTANCE_IMPORTANT_CONVERSATION = 5; // High + isImportantConversation(). } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index fd8460f9c4786683fd43c52087c834c8b1257dc6..9945057f0e941fcdb9fa61406c1690f28ca21f52 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3158,7 +3158,7 @@ @hide --> + android:protectionLevel="signature|preinstalled" /> + + + android:protectionLevel="signature|privileged|wellbeing|development" /> @@ -5025,6 +5028,10 @@ + + + @@ -5227,7 +5234,7 @@ diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml index 889a615e07f486bcd409cf00cac517b86e5a1e17..3b6b4072dbcdecf7bf676fc93c285135251f1f26 100644 --- a/core/res/res/anim/screen_rotate_180_enter.xml +++ b/core/res/res/anim/screen_rotate_180_enter.xml @@ -25,4 +25,10 @@ android:fillBefore="true" android:fillAfter="true" android:interpolator="@interpolator/fast_out_slow_in" android:duration="@android:integer/config_screen_rotation_total_180" /> + diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml index 766fcfae1f91b60bb6de13a6fdc714a3b3eb1d35..26fb6d8df506bd7e3ba6a7936aa75c5373edec9b 100644 --- a/core/res/res/anim/screen_rotate_180_exit.xml +++ b/core/res/res/anim/screen_rotate_180_exit.xml @@ -25,4 +25,9 @@ android:fillBefore="true" android:fillAfter="true" android:interpolator="@interpolator/fast_out_slow_in" android:duration="@android:integer/config_screen_rotation_total_180" /> - \ No newline at end of file + + diff --git a/core/res/res/drawable-car-night/car_dialog_button_background.xml b/core/res/res/drawable-car-night/car_dialog_button_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..138cb38b0d874f73dee42ec43604258810949b64 --- /dev/null +++ b/core/res/res/drawable-car-night/car_dialog_button_background.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + diff --git a/core/res/res/drawable-car/car_dialog_button_background.xml b/core/res/res/drawable-car/car_dialog_button_background.xml index 67506cbc12bc8d098f20d134180e882b1312deaf..a7d40bcd759a21e7c46dd2f0b4332ac11bfdbeb1 100644 --- a/core/res/res/drawable-car/car_dialog_button_background.xml +++ b/core/res/res/drawable-car/car_dialog_button_background.xml @@ -14,9 +14,19 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - + + + + + + + - + + + + + + + + diff --git a/core/res/res/drawable-nodpi/ic_number11.xml b/core/res/res/drawable-nodpi/ic_number11.xml new file mode 100644 index 0000000000000000000000000000000000000000..daad61148c80e594ea7ababa0cb81fb506ef43db --- /dev/null +++ b/core/res/res/drawable-nodpi/ic_number11.xml @@ -0,0 +1,12 @@ + + + + diff --git a/core/res/res/drawable/chooser_action_button_bg.xml b/core/res/res/drawable/chooser_action_button_bg.xml index a434c0b9b6a9b0b459e9195fdedafc2cdb7a7291..0dd9e9c7cd98b8d4f164c04aead64660873cf627 100644 --- a/core/res/res/drawable/chooser_action_button_bg.xml +++ b/core/res/res/drawable/chooser_action_button_bg.xml @@ -25,8 +25,8 @@ - + android:color="?attr/opacityListDivider" /> + diff --git a/packages/SystemUI/res/color/qs_user_detail_avatar_tint.xml b/core/res/res/drawable/chooser_dialog_background.xml similarity index 64% rename from packages/SystemUI/res/color/qs_user_detail_avatar_tint.xml rename to core/res/res/drawable/chooser_dialog_background.xml index 696e9b121564955710d39b95cc7bf8fa626b0552..b914d63187e703671eb3b24ff0c707411461e418 100644 --- a/packages/SystemUI/res/color/qs_user_detail_avatar_tint.xml +++ b/core/res/res/drawable/chooser_dialog_background.xml @@ -1,7 +1,6 @@ - - - - - + + + + \ No newline at end of file diff --git a/core/res/res/drawable/conversation_badge_background.xml b/core/res/res/drawable/conversation_badge_background.xml index 0dd0dcda40fb8ab9251713feded3d8088bf04820..9e6405dc104017995359265aa80b93ba19b81144 100644 --- a/core/res/res/drawable/conversation_badge_background.xml +++ b/core/res/res/drawable/conversation_badge_background.xml @@ -22,7 +22,7 @@ android:color="#ffffff"/> + android:width="20dp" + android:height="20dp"/> diff --git a/core/res/res/drawable/conversation_badge_ring.xml b/core/res/res/drawable/conversation_badge_ring.xml index 11ba8ad69505ee6ccf8f6453409822f94409bd2b..eee53d1c21b5d14ff9685b7469fc2b60d02d0deb 100644 --- a/core/res/res/drawable/conversation_badge_ring.xml +++ b/core/res/res/drawable/conversation_badge_ring.xml @@ -16,17 +16,18 @@ --> - - + android:shape="oval" +> + + android:width="@dimen/importance_ring_stroke_width" + /> + android:width="@dimen/importance_ring_size" + android:height="@dimen/importance_ring_size" + /> diff --git a/core/res/res/layout-car/car_alert_dialog.xml b/core/res/res/layout-car/car_alert_dialog.xml new file mode 100644 index 0000000000000000000000000000000000000000..569e5948e2e08aa78c43b1b6679c518cc5d05140 --- /dev/null +++ b/core/res/res/layout-car/car_alert_dialog.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/res/layout-car/car_alert_dialog_button_bar.xml b/core/res/res/layout-car/car_alert_dialog_button_bar.xml new file mode 100644 index 0000000000000000000000000000000000000000..277b0dcca657749689f1dbbbfcf50af9c9aae87a --- /dev/null +++ b/core/res/res/layout-car/car_alert_dialog_button_bar.xml @@ -0,0 +1,64 @@ + + + + + + +