diff options
273 files changed, 7186 insertions, 2456 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index 9b9311f6c3c2..50d23ad2d657 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -232,6 +232,9 @@ droidstubs { java_defaults { name: "framework-stubs-default", + libs: [ "stub-annotations" ], + static_libs: [ "private-stub-annotations-jar" ], + sdk_version: "core_current", errorprone: { javacflags: [ "-XepDisableAllChecks", @@ -247,62 +250,26 @@ java_defaults { java_library_static { name: "android_stubs_current", - srcs: [ - ":api-stubs-docs", - ], - libs: [ - "stub-annotations", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":api-stubs-docs" ], defaults: ["framework-stubs-default"], - sdk_version: "core_current", } java_library_static { name: "android_system_stubs_current", - srcs: [ - ":system-api-stubs-docs", - ], - libs: [ - "stub-annotations", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":system-api-stubs-docs" ], defaults: ["framework-stubs-default"], - sdk_version: "core_current", } java_library_static { name: "android_test_stubs_current", - srcs: [ - ":test-api-stubs-docs", - ], - libs: [ - "stub-annotations", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":test-api-stubs-docs" ], defaults: ["framework-stubs-default"], - sdk_version: "core_current", } java_library_static { name: "android_module_lib_stubs_current", - srcs: [ - ":module-lib-api-stubs-docs", - ], - libs: [ - "stub-annotations", - ], - static_libs: [ - "private-stub-annotations-jar", - ], + srcs: [ ":module-lib-api-stubs-docs" ], defaults: ["framework-stubs-default"], - sdk_version: "core_current", } ///////////////////////////////////////////////////////////////////// diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index 8cea645f4e71..821305e18240 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -142,6 +142,9 @@ public class BlobStoreManager { /** @hide */ public static final int COMMIT_RESULT_ERROR = 1; + /** @hide */ + public static final int INVALID_RES_ID = -1; + private final Context mContext; private final IBlobStoreManager mService; @@ -285,11 +288,65 @@ public class BlobStoreManager { * caller is trying to acquire too many leases. * * @see {@link #acquireLease(BlobHandle, int)} + * @see {@link #acquireLease(BlobHandle, CharSequence)} */ public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId, @CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException { try { - mService.acquireLease(blobHandle, descriptionResId, leaseExpiryTimeMillis, + mService.acquireLease(blobHandle, descriptionResId, null, leaseExpiryTimeMillis, + mContext.getOpPackageName()); + } catch (ParcelableException e) { + e.maybeRethrow(IOException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the + * system that the caller wants the blob to be kept around. + * + * <p> This is variant of {@link #acquireLease(BlobHandle, int, long)} taking a + * {@link CharSequence} for {@code description}. It is highly recommended that callers only + * use this when a valid resource ID for {@code description} could not be provided. Otherwise, + * apps should prefer using {@link #acquireLease(BlobHandle, int)} which will allow + * {@code description} to be localized. + * + * <p> Any active leases will be automatically released when the blob's expiry time + * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. + * + * <p> This lease information is persisted and calling this more than once will result in + * latest lease overriding any previous lease. + * + * @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. + * @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 + * API is identical to {@link #acquireLease(BlobHandle, int)} + * where clients have to explicitly call + * {@link #releaseLease(BlobHandle)} when they don't + * need the blob anymore. + * + * @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 or + * if the {@code leaseExpiryTimeMillis} is greater than the + * {@link BlobHandle#getExpiryTimeMillis()}. + * @throws IllegalStateException when a lease could not be acquired, such as when the + * caller is trying to acquire too many leases. + * + * @see {@link #acquireLease(BlobHandle, int, long)} + * @see {@link #acquireLease(BlobHandle, CharSequence)} + */ + public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description, + @CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException { + try { + mService.acquireLease(blobHandle, INVALID_RES_ID, description, leaseExpiryTimeMillis, mContext.getOpPackageName()); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); @@ -327,6 +384,7 @@ public class BlobStoreManager { * caller is trying to acquire too many leases. * * @see {@link #acquireLease(BlobHandle, int, long)} + * @see {@link #acquireLease(BlobHandle, CharSequence, long)} */ public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId) throws IOException { @@ -334,6 +392,47 @@ public class BlobStoreManager { } /** + * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the + * system that the caller wants the blob to be kept around. + * + * <p> This is variant of {@link #acquireLease(BlobHandle, int)} taking a {@link CharSequence} + * for {@code description}. It is highly recommended that callers only use this when a valid + * resource ID for {@code description} could not be provided. Otherwise, apps should prefer + * using {@link #acquireLease(BlobHandle, int)} which will allow {@code description} to be + * localized. + * + * <p> This is similar to {@link #acquireLease(BlobHandle, CharSequence, long)} except clients + * don't have to specify the lease expiry time upfront using this API and need to explicitly + * release the lease using {@link #releaseLease(BlobHandle)} when they no longer like to keep + * a blob around. + * + * <p> Any active leases will be automatically released when the blob's expiry time + * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. + * + * <p> This lease information is persisted and calling this more than once will result in + * latest lease overriding any previous lease. + * + * @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. + * + * @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 IllegalStateException when a lease could not be acquired, such as when the + * caller is trying to acquire too many leases. + * + * @see {@link #acquireLease(BlobHandle, int)} + * @see {@link #acquireLease(BlobHandle, CharSequence, long)} + */ + public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description) + throws IOException { + acquireLease(blobHandle, description, 0); + } + + /** * Release all active leases to the blob represented by {@code blobHandle} which are * currently held by the caller. * diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl index e2128b421746..a85a25c9c4ad 100644 --- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl +++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl @@ -26,8 +26,8 @@ interface IBlobStoreManager { ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName); void deleteSession(long sessionId, in String packageName); - void acquireLease(in BlobHandle handle, int descriptionResId, long leaseTimeout, - in String packageName); + void acquireLease(in BlobHandle handle, int descriptionResId, in CharSequence description, + long leaseTimeoutMillis, in String packageName); void releaseLease(in BlobHandle handle, in String packageName); void waitForIdle(in RemoteCallback callback); diff --git a/apex/blobstore/framework/java/android/app/blob/XmlTags.java b/apex/blobstore/framework/java/android/app/blob/XmlTags.java index 803c9a40e5ea..9834d7477838 100644 --- a/apex/blobstore/framework/java/android/app/blob/XmlTags.java +++ b/apex/blobstore/framework/java/android/app/blob/XmlTags.java @@ -52,4 +52,5 @@ public final class XmlTags { // For leasee public static final String TAG_LEASEE = "l"; public static final String ATTR_DESCRIPTION_RES_ID = "rid"; + public static final String ATTR_DESCRIPTION = "d"; } 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 c12e0ec8aec9..c7d803c88410 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -15,6 +15,7 @@ */ package com.android.server.blob; +import static android.app.blob.XmlTags.ATTR_DESCRIPTION; import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_ID; import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME; import static android.app.blob.XmlTags.ATTR_ID; @@ -28,12 +29,14 @@ import static android.app.blob.XmlTags.TAG_LEASEE; import static android.system.OsConstants.O_RDONLY; import static com.android.server.blob.BlobStoreConfig.TAG; +import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.blob.BlobHandle; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.ResourceId; import android.content.res.Resources; import android.os.ParcelFileDescriptor; import android.os.RevocableFileDescriptor; @@ -141,11 +144,11 @@ class BlobMetadata { } } - void addLeasee(String callingPackage, int callingUid, - int descriptionResId, long leaseExpiryTimeMillis) { + void addLeasee(String callingPackage, int callingUid, int descriptionResId, + CharSequence description, long leaseExpiryTimeMillis) { synchronized (mMetadataLock) { mLeasees.add(new Leasee(callingPackage, callingUid, - descriptionResId, leaseExpiryTimeMillis)); + descriptionResId, description, leaseExpiryTimeMillis)); } } @@ -308,7 +311,7 @@ class BlobMetadata { } @Nullable - static BlobMetadata createFromXml(Context context, XmlPullParser in) + static BlobMetadata createFromXml(XmlPullParser in, int version, Context context) throws XmlPullParserException, IOException { final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID); final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID); @@ -321,12 +324,12 @@ class BlobMetadata { if (TAG_BLOB_HANDLE.equals(in.getName())) { blobHandle = BlobHandle.createFromXml(in); } else if (TAG_COMMITTER.equals(in.getName())) { - final Committer committer = Committer.createFromXml(in); + final Committer committer = Committer.createFromXml(in, version); if (committer != null) { committers.add(committer); } } else if (TAG_LEASEE.equals(in.getName())) { - leasees.add(Leasee.createFromXml(in)); + leasees.add(Leasee.createFromXml(in, version)); } } @@ -366,7 +369,7 @@ class BlobMetadata { } @Nullable - static Committer createFromXml(@NonNull XmlPullParser in) + static Committer createFromXml(@NonNull XmlPullParser in, int version) throws XmlPullParserException, IOException { final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); @@ -388,12 +391,15 @@ class BlobMetadata { static final class Leasee extends Accessor { public final int descriptionResId; + public final CharSequence description; public final long expiryTimeMillis; - Leasee(String packageName, int uid, int descriptionResId, long expiryTimeMillis) { + Leasee(String packageName, int uid, int descriptionResId, CharSequence description, + long expiryTimeMillis) { super(packageName, uid); this.descriptionResId = descriptionResId; this.expiryTimeMillis = expiryTimeMillis; + this.description = description; } boolean isStillValid() { @@ -401,18 +407,27 @@ class BlobMetadata { } void dump(Context context, IndentingPrintWriter fout) { + fout.println("desc: " + getDescriptionToDump(context)); + fout.println("expiryMs: " + expiryTimeMillis); + } + + private String getDescriptionToDump(Context context) { String desc = null; - try { - final Resources leaseeRes = context.getPackageManager() - .getResourcesForApplicationAsUser(packageName, UserHandle.getUserId(uid)); - desc = leaseeRes.getString(descriptionResId); - } catch (PackageManager.NameNotFoundException e) { - Slog.d(TAG, "Unknown package in user " + UserHandle.getUserId(uid) + ": " - + packageName, e); - desc = "<none>"; + if (ResourceId.isValid(descriptionResId)) { + try { + final Resources leaseeRes = context.getPackageManager() + .getResourcesForApplicationAsUser( + packageName, UserHandle.getUserId(uid)); + desc = leaseeRes.getString(descriptionResId); + } catch (PackageManager.NameNotFoundException e) { + Slog.d(TAG, "Unknown package in user " + UserHandle.getUserId(uid) + ": " + + packageName, e); + desc = "<none>"; + } + } else { + desc = description.toString(); } - fout.println("desc: " + desc); - fout.println("expiryMs: " + expiryTimeMillis); + return desc; } void writeToXml(@NonNull XmlSerializer out) throws IOException { @@ -420,16 +435,23 @@ class BlobMetadata { XmlUtils.writeIntAttribute(out, ATTR_UID, uid); XmlUtils.writeIntAttribute(out, ATTR_DESCRIPTION_RES_ID, descriptionResId); XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis); + XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION, description); } @NonNull - static Leasee createFromXml(@NonNull XmlPullParser in) throws IOException { + static Leasee createFromXml(@NonNull XmlPullParser in, int version) throws IOException { final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); final int descriptionResId = XmlUtils.readIntAttribute(in, ATTR_DESCRIPTION_RES_ID); final long expiryTimeMillis = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME); + final CharSequence description; + if (version >= XML_VERSION_ADD_STRING_DESC) { + description = XmlUtils.readStringAttribute(in, ATTR_DESCRIPTION); + } else { + description = null; + } - return new Leasee(packageName, uid, descriptionResId, expiryTimeMillis); + return new Leasee(packageName, uid, descriptionResId, description, expiryTimeMillis); } } 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 ba2e559afdab..bcc1610435d9 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -28,7 +28,12 @@ class BlobStoreConfig { public static final String TAG = "BlobStore"; public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE); - public static final int CURRENT_XML_VERSION = 1; + // Initial version. + public static final int XML_VERSION_INIT = 1; + // Added a string variant of lease description. + public static final int XML_VERSION_ADD_STRING_DESC = 2; + + public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_STRING_DESC; private static final String ROOT_DIR_NAME = "blobstore"; private static final String BLOBS_DIR_NAME = "blobs"; 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 0ba34cab6560..1efdbda97fe5 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -27,10 +27,10 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.os.UserHandle.USER_NULL; -import static com.android.server.blob.BlobStoreConfig.CURRENT_XML_VERSION; import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS; import static com.android.server.blob.BlobStoreConfig.TAG; +import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT; 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; @@ -324,7 +324,8 @@ public class BlobStoreManagerService extends SystemService { } private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId, - long leaseExpiryTimeMillis, int callingUid, String callingPackage) { + CharSequence description, long leaseExpiryTimeMillis, + int callingUid, String callingPackage) { synchronized (mBlobsLock) { final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid)) .get(blobHandle); @@ -338,7 +339,7 @@ public class BlobStoreManagerService extends SystemService { "Lease expiry cannot be later than blobs expiry time"); } blobMetadata.addLeasee(callingPackage, callingUid, - descriptionResId, leaseExpiryTimeMillis); + descriptionResId, description, leaseExpiryTimeMillis); if (LOGV) { Slog.v(TAG, "Acquired lease on " + blobHandle + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); @@ -450,7 +451,7 @@ public class BlobStoreManagerService extends SystemService { out.setOutput(fos, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, TAG_SESSIONS); - XmlUtils.writeIntAttribute(out, ATTR_VERSION, CURRENT_XML_VERSION); + XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { final LongSparseArray<BlobStoreSession> userSessions = @@ -491,6 +492,7 @@ public class BlobStoreManagerService extends SystemService { final XmlPullParser in = Xml.newPullParser(); in.setInput(fis, StandardCharsets.UTF_8.name()); XmlUtils.beginDocument(in, TAG_SESSIONS); + final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION); while (true) { XmlUtils.nextElement(in); if (in.getEventType() == XmlPullParser.END_DOCUMENT) { @@ -499,7 +501,7 @@ public class BlobStoreManagerService extends SystemService { if (TAG_SESSION.equals(in.getName())) { final BlobStoreSession session = BlobStoreSession.createFromXml( - in, mContext, mSessionStateChangeListener); + in, version, mContext, mSessionStateChangeListener); if (session == null) { continue; } @@ -539,7 +541,7 @@ public class BlobStoreManagerService extends SystemService { out.setOutput(fos, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, TAG_BLOBS); - XmlUtils.writeIntAttribute(out, ATTR_VERSION, CURRENT_XML_VERSION); + XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); @@ -579,6 +581,7 @@ public class BlobStoreManagerService extends SystemService { final XmlPullParser in = Xml.newPullParser(); in.setInput(fis, StandardCharsets.UTF_8.name()); XmlUtils.beginDocument(in, TAG_BLOBS); + final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION); while (true) { XmlUtils.nextElement(in); if (in.getEventType() == XmlPullParser.END_DOCUMENT) { @@ -586,7 +589,8 @@ public class BlobStoreManagerService extends SystemService { } if (TAG_BLOB.equals(in.getName())) { - final BlobMetadata blobMetadata = BlobMetadata.createFromXml(mContext, in); + final BlobMetadata blobMetadata = BlobMetadata.createFromXml( + in, version, mContext); final SparseArray<String> userPackages = allPackages.get( blobMetadata.getUserId()); if (userPackages == null) { @@ -1032,11 +1036,14 @@ public class BlobStoreManagerService extends SystemService { @Override public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId, + @Nullable CharSequence description, @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) { Objects.requireNonNull(blobHandle, "blobHandle must not be null"); blobHandle.assertIsValid(); - Preconditions.checkArgument(ResourceId.isValid(descriptionResId), - "descriptionResId is not valid"); + Preconditions.checkArgument( + ResourceId.isValid(descriptionResId) || description != null, + "Description must be valid; descriptionId=" + descriptionResId + + ", description=" + description); Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis, "leaseExpiryTimeMillis must not be negative"); Objects.requireNonNull(packageName, "packageName must not be null"); @@ -1044,7 +1051,7 @@ public class BlobStoreManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); - acquireLeaseInternal(blobHandle, descriptionResId, leaseExpiryTimeMillis, + acquireLeaseInternal(blobHandle, descriptionResId, description, leaseExpiryTimeMillis, callingUid, packageName); } 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 bd35b86babd8..80b42355ef7c 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -511,7 +511,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } @Nullable - static BlobStoreSession createFromXml(@NonNull XmlPullParser in, + static BlobStoreSession createFromXml(@NonNull XmlPullParser in, int version, @NonNull Context context, @NonNull SessionStateChangeListener stateChangeListener) throws IOException, XmlPullParserException { final int sessionId = XmlUtils.readIntAttribute(in, ATTR_ID); diff --git a/api/current.txt b/api/current.txt index 826d409b0a2d..475506a317bc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7574,7 +7574,9 @@ package android.app.blob { public class BlobStoreManager { method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int, long) throws java.io.IOException; + method public void acquireLease(@NonNull android.app.blob.BlobHandle, @NonNull CharSequence, long) throws java.io.IOException; method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int) throws java.io.IOException; + method public void acquireLease(@NonNull android.app.blob.BlobHandle, @NonNull CharSequence) throws java.io.IOException; method @IntRange(from=1) public long createSession(@NonNull android.app.blob.BlobHandle) throws java.io.IOException; method public void deleteSession(@IntRange(from=1) long) throws java.io.IOException; method @NonNull public android.os.ParcelFileDescriptor openBlob(@NonNull android.app.blob.BlobHandle) throws java.io.IOException; @@ -12575,6 +12577,7 @@ package android.content.res { method public int getLayoutDirection(); method @NonNull public android.os.LocaleList getLocales(); method public boolean isLayoutSizeAtLeast(int); + method public boolean isNightModeActive(); method public boolean isScreenHdr(); method public boolean isScreenRound(); method public boolean isScreenWideColorGamut(); @@ -17032,6 +17035,7 @@ package android.hardware.biometrics { field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1 field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc + field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf field public static final int BIOMETRIC_SUCCESS = 0; // 0x0 } @@ -17066,6 +17070,7 @@ package android.hardware.biometrics { field public static final int BIOMETRIC_ERROR_NO_BIOMETRICS = 11; // 0xb field public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; // 0xe field public static final int BIOMETRIC_ERROR_NO_SPACE = 4; // 0x4 + field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf field public static final int BIOMETRIC_ERROR_TIMEOUT = 3; // 0x3 field public static final int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 field public static final int BIOMETRIC_ERROR_USER_CANCELED = 10; // 0xa @@ -31280,6 +31285,7 @@ package android.net.wifi { method public int getWifiState(); method public boolean is5GHzBandSupported(); method public boolean is6GHzBandSupported(); + method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isAutoWakeupEnabled(); method @Deprecated public boolean isDeviceToApRttSupported(); method public boolean isEasyConnectSupported(); method public boolean isEnhancedOpenSupported(); @@ -42599,6 +42605,7 @@ package android.security.keystore { method @NonNull public String getKeystoreAlias(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); + method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method @NonNull public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); @@ -42633,9 +42640,10 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean); + method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationParameters(@IntRange(from=0xffffffff) int, int); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean); - method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int); + method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserConfirmationRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUserPresenceRequired(boolean); } @@ -42652,6 +42660,7 @@ package android.security.keystore { method public int getOrigin(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); + method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method public boolean isInsideSecureHardware(); method public boolean isInvalidatedByBiometricEnrollment(); @@ -42675,6 +42684,8 @@ package android.security.keystore { } public abstract class KeyProperties { + field public static final int AUTH_BIOMETRIC_STRONG = 2; // 0x2 + field public static final int AUTH_DEVICE_CREDENTIAL = 1; // 0x1 field public static final String BLOCK_MODE_CBC = "CBC"; field public static final String BLOCK_MODE_CTR = "CTR"; field public static final String BLOCK_MODE_ECB = "ECB"; @@ -42721,6 +42732,7 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityStart(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); + method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); @@ -42746,9 +42758,10 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean); + method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationParameters(@IntRange(from=0xffffffff) int, int); method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean); - method @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int); + method @Deprecated @NonNull public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(@IntRange(from=0xffffffff) int); method @NonNull public android.security.keystore.KeyProtection.Builder setUserConfirmationRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setUserPresenceRequired(boolean); } @@ -45426,7 +45439,6 @@ package android.telecom { field public static final int PROPERTY_GENERIC_CONFERENCE = 2; // 0x2 field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 128; // 0x80 field public static final int PROPERTY_HIGH_DEF_AUDIO = 16; // 0x10 - field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 8192; // 0x2000 field public static final int PROPERTY_IS_EXTERNAL_CALL = 64; // 0x40 field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 2048; // 0x800 field public static final int PROPERTY_RTT = 1024; // 0x400 @@ -45504,7 +45516,6 @@ package android.telecom { public abstract class Conference extends android.telecom.Conferenceable { ctor public Conference(android.telecom.PhoneAccountHandle); method public final boolean addConnection(android.telecom.Connection); - method @NonNull public static android.telecom.Conference createFailedConference(@NonNull android.telecom.DisconnectCause, @NonNull android.telecom.PhoneAccountHandle); method public final void destroy(); method public final android.telecom.CallAudioState getCallAudioState(); method public final java.util.List<android.telecom.Connection> getConferenceableConnections(); @@ -45519,8 +45530,6 @@ package android.telecom { method public final android.telecom.StatusHints getStatusHints(); method public android.telecom.Connection.VideoProvider getVideoProvider(); method public int getVideoState(); - method public final boolean isRingbackRequested(); - method public void onAnswer(int); method public void onCallAudioStateChanged(android.telecom.CallAudioState); method public void onConnectionAdded(android.telecom.Connection); method public void onDisconnect(); @@ -45529,7 +45538,6 @@ package android.telecom { method public void onMerge(android.telecom.Connection); method public void onMerge(); method public void onPlayDtmfTone(char); - method public void onReject(); method public void onSeparate(android.telecom.Connection); method public void onStopDtmfTone(); method public void onSwap(); @@ -45549,8 +45557,6 @@ package android.telecom { method public final void setDisconnected(android.telecom.DisconnectCause); method public final void setExtras(@Nullable android.os.Bundle); method public final void setOnHold(); - method public final void setRingbackRequested(boolean); - method public final void setRinging(); method public final void setStatusHints(android.telecom.StatusHints); method public final void setVideoProvider(android.telecom.Connection, android.telecom.Connection.VideoProvider); method public final void setVideoState(android.telecom.Connection, int); @@ -45709,7 +45715,6 @@ package android.telecom { field public static final int PROPERTY_ASSISTED_DIALING_USED = 512; // 0x200 field public static final int PROPERTY_HAS_CDMA_VOICE_PRIVACY = 32; // 0x20 field public static final int PROPERTY_HIGH_DEF_AUDIO = 4; // 0x4 - field public static final int PROPERTY_IS_ADHOC_CONFERENCE = 4096; // 0x1000 field public static final int PROPERTY_IS_EXTERNAL_CALL = 16; // 0x10 field public static final int PROPERTY_IS_RTT = 256; // 0x100 field public static final int PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL = 1024; // 0x400 @@ -46125,7 +46130,6 @@ package android.telecom { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(int); method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle); - method public void addNewIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.os.Bundle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void cancelMissedCallsNotification(); method public android.content.Intent createManageBlockedNumbersIntent(); method @Deprecated @RequiresPermission(android.Manifest.permission.ANSWER_PHONE_CALLS) public boolean endCall(); @@ -46153,7 +46157,6 @@ package android.telecom { method public void registerPhoneAccount(android.telecom.PhoneAccount); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger(); - method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull java.util.List<android.net.Uri>, @NonNull android.os.Bundle); method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); field public static final String ACTION_CHANGE_DEFAULT_DIALER = "android.telecom.action.CHANGE_DEFAULT_DIALER"; field public static final String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; @@ -46680,7 +46683,6 @@ package android.telephony { field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; - field public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = "support_adhoc_conference_calls_bool"; field public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool"; field public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"; field public static final String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool"; diff --git a/api/system-current.txt b/api/system-current.txt index 24936d5784f8..a428d13fc6d9 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1451,16 +1451,16 @@ package android.app.usage { package android.bluetooth { public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile { - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void disableOptionalCodecs(@Nullable android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void enableOptionalCodecs(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void disableOptionalCodecs(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void enableOptionalCodecs(@NonNull android.bluetooth.BluetoothDevice); method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothDevice getActiveDevice(); - method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothCodecStatus getCodecStatus(@Nullable android.bluetooth.BluetoothDevice); + method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothCodecStatus getCodecStatus(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int getOptionalCodecsEnabled(@Nullable android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setCodecConfigPreference(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothCodecConfig); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int isOptionalCodecsEnabled(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int isOptionalCodecsSupported(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setCodecConfigPreference(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothCodecConfig); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setOptionalCodecsEnabled(@Nullable android.bluetooth.BluetoothDevice, int); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int supportsOptionalCodecs(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setOptionalCodecsEnabled(@NonNull android.bluetooth.BluetoothDevice, int); field public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0; // 0x0 field public static final int OPTIONAL_CODECS_PREF_DISABLED = 0; // 0x0 field public static final int OPTIONAL_CODECS_PREF_ENABLED = 1; // 0x1 @@ -1471,10 +1471,10 @@ package android.bluetooth { public final class BluetoothA2dpSink implements android.bluetooth.BluetoothProfile { method public void finalize(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int); - field @RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED"; + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isAudioPlaying(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int); + field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED"; } public final class BluetoothAdapter { @@ -1647,9 +1647,9 @@ package android.bluetooth { } public class BluetoothPbap implements android.bluetooth.BluetoothProfile { - method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); - field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); + field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; } public interface BluetoothProfile { @@ -1922,18 +1922,22 @@ package android.content.integrity { public abstract class IntegrityFormula { method @NonNull public static android.content.integrity.IntegrityFormula all(@NonNull android.content.integrity.IntegrityFormula...); method @NonNull public static android.content.integrity.IntegrityFormula any(@NonNull android.content.integrity.IntegrityFormula...); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(@NonNull String); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(boolean); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(long); - method @NonNull public android.content.integrity.IntegrityFormula greaterThan(long); - method @NonNull public android.content.integrity.IntegrityFormula greaterThanOrEquals(long); method @NonNull public static android.content.integrity.IntegrityFormula not(@NonNull android.content.integrity.IntegrityFormula); - field @NonNull public static final android.content.integrity.IntegrityFormula APP_CERTIFICATE; - field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_CERTIFICATE; - field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_NAME; - field @NonNull public static final android.content.integrity.IntegrityFormula PACKAGE_NAME; - field @NonNull public static final android.content.integrity.IntegrityFormula PRE_INSTALLED; - field @NonNull public static final android.content.integrity.IntegrityFormula VERSION_CODE; + } + + public static final class IntegrityFormula.Application { + method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula isPreInstalled(); + method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeEquals(@NonNull long); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThan(@NonNull long); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long); + } + + public static final class IntegrityFormula.Installer { + method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula notAllowedByManifest(); + method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); } public final class Rule implements android.os.Parcelable { @@ -2007,18 +2011,15 @@ package android.content.pm { } public final class InstallationFile implements android.os.Parcelable { - ctor public InstallationFile(@NonNull String, long, @Nullable byte[]); + ctor public InstallationFile(int, @NonNull String, long, @Nullable byte[], @Nullable byte[]); method public int describeContents(); - method public int getFileType(); + method public long getLengthBytes(); + method public int getLocation(); method @Nullable public byte[] getMetadata(); method @NonNull public String getName(); - method public long getSize(); + method @Nullable public byte[] getSignature(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.InstallationFile> CREATOR; - field public static final int FILE_TYPE_APK = 0; // 0x0 - field public static final int FILE_TYPE_LIB = 1; // 0x1 - field public static final int FILE_TYPE_OBB = 2; // 0x2 - field public static final int FILE_TYPE_UNKNOWN = -1; // 0xffffffff } public final class InstantAppInfo implements android.os.Parcelable { @@ -4854,6 +4855,8 @@ package android.media.tv.tuner { public class Tuner implements java.lang.AutoCloseable { ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @NonNull String, int, @Nullable android.media.tv.tuner.Tuner.OnResourceLostListener); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelScanning(); + method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelTuning(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void clearOnTuneEventListener(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int connectCiCam(int); @@ -4874,8 +4877,6 @@ package android.media.tv.tuner { method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setLna(boolean); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner); - method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopScan(); - method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int stopTune(); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings); method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void updateResourcePriority(int, int); } @@ -7717,6 +7718,7 @@ package android.net.wifi { method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveBackupData(); method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public byte[] retrieveSoftApBackupData(); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener); + method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setAutoWakeupEnabled(boolean); method @RequiresPermission(android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE) public void setDeviceMobilityState(int); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMacRandomizationSettingPasspointEnabled(@NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void setMeteredOverridePasspoint(@NonNull String, int); @@ -8242,8 +8244,8 @@ package android.net.wifi.wificond { public final class NativeScanResult implements android.os.Parcelable { ctor public NativeScanResult(); method public int describeContents(); - method @NonNull public byte[] getBssid(); - method @NonNull public int getCapabilities(); + method @Nullable public android.net.MacAddress getBssid(); + method public int getCapabilities(); method public int getFrequencyMhz(); method @NonNull public byte[] getInformationElements(); method @NonNull public java.util.List<android.net.wifi.wificond.RadioChainInfo> getRadioChainInfos(); @@ -8252,15 +8254,31 @@ package android.net.wifi.wificond { method public long getTsf(); method public boolean isAssociated(); method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int BSS_CAPABILITY_APSD = 2048; // 0x800 + field public static final int BSS_CAPABILITY_CF_POLLABLE = 4; // 0x4 + field public static final int BSS_CAPABILITY_CF_POLL_REQUEST = 8; // 0x8 + field public static final int BSS_CAPABILITY_CHANNEL_AGILITY = 128; // 0x80 + field public static final int BSS_CAPABILITY_DELAYED_BLOCK_ACK = 16384; // 0x4000 + field public static final int BSS_CAPABILITY_DSSS_OFDM = 8192; // 0x2000 + field public static final int BSS_CAPABILITY_ESS = 1; // 0x1 + field public static final int BSS_CAPABILITY_IBSS = 2; // 0x2 + field public static final int BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK = 32768; // 0x8000 + field public static final int BSS_CAPABILITY_PBCC = 64; // 0x40 + field public static final int BSS_CAPABILITY_PRIVACY = 16; // 0x10 + field public static final int BSS_CAPABILITY_QOS = 512; // 0x200 + field public static final int BSS_CAPABILITY_RADIO_MANAGEMENT = 4096; // 0x1000 + field public static final int BSS_CAPABILITY_SHORT_PREAMBLE = 32; // 0x20 + field public static final int BSS_CAPABILITY_SHORT_SLOT_TIME = 1024; // 0x400 + field public static final int BSS_CAPABILITY_SPECTRUM_MANAGEMENT = 256; // 0x100 field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.NativeScanResult> CREATOR; } public final class NativeWifiClient implements android.os.Parcelable { - ctor public NativeWifiClient(@NonNull byte[]); + ctor public NativeWifiClient(@Nullable android.net.MacAddress); method public int describeContents(); + method @Nullable public android.net.MacAddress getMacAddress(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.wificond.NativeWifiClient> CREATOR; - field @NonNull public final byte[] macAddress; } public final class PnoNetwork implements android.os.Parcelable { @@ -8305,7 +8323,7 @@ package android.net.wifi.wificond { public class WifiCondManager { method public void abortScan(@NonNull String); method public void enableVerboseLogging(boolean); - method @NonNull public java.util.List<java.lang.Integer> getChannelsMhzForBand(int); + method @NonNull public int[] getChannelsMhzForBand(int); method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String); method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int); method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String); @@ -9680,7 +9698,6 @@ package android.provider { field public static final String EUICC_UNSUPPORTED_COUNTRIES = "euicc_unsupported_countries"; field public static final String INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT = "install_carrier_app_notification_persistent"; field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis"; - field public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled"; field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update"; field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt"; field public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled"; @@ -9695,7 +9712,7 @@ package android.provider { field public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled"; field public static final String WIFI_SCORE_PARAMS = "wifi_score_params"; field public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled"; - field public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled"; + field @Deprecated public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled"; } public static final class Settings.Secure extends android.provider.Settings.NameValueTable { @@ -10317,7 +10334,7 @@ package android.service.dataloader { public abstract class DataLoaderService extends android.app.Service { ctor public DataLoaderService(); - method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(); + method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams); } public static interface DataLoaderService.DataLoader { diff --git a/api/test-current.txt b/api/test-current.txt index e352cb65156e..78a0b0a2046c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -812,18 +812,22 @@ package android.content.integrity { public abstract class IntegrityFormula { method @NonNull public static android.content.integrity.IntegrityFormula all(@NonNull android.content.integrity.IntegrityFormula...); method @NonNull public static android.content.integrity.IntegrityFormula any(@NonNull android.content.integrity.IntegrityFormula...); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(@NonNull String); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(boolean); - method @NonNull public android.content.integrity.IntegrityFormula equalTo(long); - method @NonNull public android.content.integrity.IntegrityFormula greaterThan(long); - method @NonNull public android.content.integrity.IntegrityFormula greaterThanOrEquals(long); method @NonNull public static android.content.integrity.IntegrityFormula not(@NonNull android.content.integrity.IntegrityFormula); - field @NonNull public static final android.content.integrity.IntegrityFormula APP_CERTIFICATE; - field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_CERTIFICATE; - field @NonNull public static final android.content.integrity.IntegrityFormula INSTALLER_NAME; - field @NonNull public static final android.content.integrity.IntegrityFormula PACKAGE_NAME; - field @NonNull public static final android.content.integrity.IntegrityFormula PRE_INSTALLED; - field @NonNull public static final android.content.integrity.IntegrityFormula VERSION_CODE; + } + + public static final class IntegrityFormula.Application { + method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula isPreInstalled(); + method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeEquals(@NonNull long); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThan(@NonNull long); + method @NonNull public static android.content.integrity.IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long); + } + + public static final class IntegrityFormula.Installer { + method @NonNull public static android.content.integrity.IntegrityFormula certificatesContain(@NonNull String); + method @NonNull public static android.content.integrity.IntegrityFormula notAllowedByManifest(); + method @NonNull public static android.content.integrity.IntegrityFormula packageNameEquals(@NonNull String); } public final class Rule implements android.os.Parcelable { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 89b1798587f0..23f9f22c94f2 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -7960,6 +7960,10 @@ message SurfaceflingerStatsLayerInfo { // presentation, until the buffer was ready to be presented. optional FrameTimingHistogram post_to_acquire = 9 [(android.os.statsd.log_mode) = MODE_BYTES]; + // Frames missed latch because the acquire fence didn't fire + optional int64 late_acquire_frames = 10; + // Frames latched early because the desired present time was bad + optional int64 bad_desired_present_frames = 11; } /** diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java index d79740b49b3d..9912d2b1cc8b 100644 --- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java @@ -76,6 +76,16 @@ public final class AccessibilityShortcutInfo { private final int mDescriptionResId; /** + * Resource id of the animated image of the accessibility shortcut target. + */ + private final int mAnimatedImageRes; + + /** + * Resource id of the html description of the accessibility shortcut target. + */ + private final int mHtmlDescriptionRes; + + /** * Creates a new instance. * * @param context Context for accessing resources. @@ -119,6 +129,14 @@ public final class AccessibilityShortcutInfo { // Gets summary mSummaryResId = asAttributes.getResourceId( com.android.internal.R.styleable.AccessibilityShortcutTarget_summary, 0); + // Gets animated image + mAnimatedImageRes = asAttributes.getResourceId( + com.android.internal.R.styleable + .AccessibilityShortcutTarget_animatedImageDrawable, 0); + // Gets html description + mHtmlDescriptionRes = asAttributes.getResourceId( + com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription, + 0); asAttributes.recycle(); if (mDescriptionResId == 0 || mSummaryResId == 0) { @@ -172,6 +190,25 @@ public final class AccessibilityShortcutInfo { } /** + * The animated image resource id of the accessibility shortcut target. + * + * @return The animated image resource id. + */ + public int getAnimatedImageRes() { + return mAnimatedImageRes; + } + + /** + * The localized html description of the accessibility shortcut target. + * + * @return The localized html description. + */ + @Nullable + public String loadHtmlDescription(@NonNull PackageManager packageManager) { + return loadResourceString(packageManager, mActivityInfo, mHtmlDescriptionRes); + } + + /** * Gets string resource by the given activity and resource id. */ @Nullable diff --git a/core/java/android/app/ITaskOrganizerController.aidl b/core/java/android/app/ITaskOrganizerController.aidl index 5d5956e4dca4..9d6c3d64aaf2 100644 --- a/core/java/android/app/ITaskOrganizerController.aidl +++ b/core/java/android/app/ITaskOrganizerController.aidl @@ -52,7 +52,11 @@ interface ITaskOrganizerController { boolean deleteRootTask(IWindowContainer task); /** Gets direct child tasks (ordered from top-to-bottom) */ - List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent); + List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent, + in int[] activityTypes); + + /** Gets all root tasks on a display (ordered from top-to-bottom) */ + List<ActivityManager.RunningTaskInfo> getRootTasks(int displayId, in int[] activityTypes); /** Get the root task which contains the current ime target */ IWindowContainer getImeTarget(int display); diff --git a/core/java/android/app/PictureInPictureParams.java b/core/java/android/app/PictureInPictureParams.java index bfd966c575a9..67d94dec88c5 100644 --- a/core/java/android/app/PictureInPictureParams.java +++ b/core/java/android/app/PictureInPictureParams.java @@ -247,6 +247,14 @@ public final class PictureInPictureParams implements Parcelable { return mSourceRectHint != null && !mSourceRectHint.isEmpty(); } + /** + * @return True if no parameters are set + * @hide + */ + public boolean empty() { + return !hasSourceBoundsHint() && !hasSetActions() && !hasSetAspectRatio(); + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 662ca6eb2c19..f7d712dbd4fb 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -149,6 +149,13 @@ public class TaskInfo { public IWindowContainer token; /** + * The PictureInPictureParams for the Task, if set. + * @hide + */ + @Nullable + public PictureInPictureParams pictureInPictureParams; + + /** * The activity type of the top activity in this task. * @hide */ @@ -209,6 +216,9 @@ public class TaskInfo { configuration.readFromParcel(source); token = IWindowContainer.Stub.asInterface(source.readStrongBinder()); topActivityType = source.readInt(); + pictureInPictureParams = source.readInt() != 0 + ? PictureInPictureParams.CREATOR.createFromParcel(source) + : null; } /** @@ -246,6 +256,12 @@ public class TaskInfo { configuration.writeToParcel(dest, flags); dest.writeStrongInterface(token); dest.writeInt(topActivityType); + if (pictureInPictureParams == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + pictureInPictureParams.writeToParcel(dest, flags); + } } @Override @@ -261,6 +277,7 @@ public class TaskInfo { + " supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow + " resizeMode=" + resizeMode + " token=" + token - + " topActivityType=" + topActivityType; + + " topActivityType=" + topActivityType + + " pictureInPictureParams=" + pictureInPictureParams; } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 9cec514ec592..dc15b51a442c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -8951,7 +8951,8 @@ public class DevicePolicyManager { * * <strong>Note: Starting from Android R, apps should no longer call this method with the * setting {@link android.provider.Settings.Secure#LOCATION_MODE}, which is deprecated. Instead, - * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}. + * device owners should call {@link #setLocationEnabled(ComponentName, boolean)}. This will be + * enforced for all apps targeting Android R or above. * </strong> * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. @@ -8961,6 +8962,7 @@ public class DevicePolicyManager { */ public void setSecureSetting(@NonNull ComponentName admin, String setting, String value) { throwIfParentInstance("setSecureSetting"); + if (mService != null) { try { mService.setSecureSetting(admin, setting, value); diff --git a/core/java/android/app/compat/TEST_MAPPING b/core/java/android/app/compat/TEST_MAPPING new file mode 100644 index 000000000000..c047df514e8d --- /dev/null +++ b/core/java/android/app/compat/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "frameworks/base/services/core/java/com/android/services/compat" + } + ] +}
\ No newline at end of file diff --git a/core/java/android/app/prediction/AppPredictionSessionId.java b/core/java/android/app/prediction/AppPredictionSessionId.java index e5e06f859ac6..876bafdfb7d1 100644 --- a/core/java/android/app/prediction/AppPredictionSessionId.java +++ b/core/java/android/app/prediction/AppPredictionSessionId.java @@ -22,6 +22,8 @@ import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * The id for an app prediction session. See {@link AppPredictor}. * @@ -32,18 +34,28 @@ import android.os.Parcelable; public final class AppPredictionSessionId implements Parcelable { private final String mId; + private final int mUserId; /** * Creates a new id for a prediction session. * * @hide */ - public AppPredictionSessionId(@NonNull String id) { + public AppPredictionSessionId(@NonNull final String id, final int userId) { mId = id; + mUserId = userId; } private AppPredictionSessionId(Parcel p) { mId = p.readString(); + mUserId = p.readInt(); + } + + /** + * @hide + */ + public int getUserId() { + return mUserId; } @Override @@ -51,17 +63,17 @@ public final class AppPredictionSessionId implements Parcelable { if (!getClass().equals(o != null ? o.getClass() : null)) return false; AppPredictionSessionId other = (AppPredictionSessionId) o; - return mId.equals(other.mId); + return mId.equals(other.mId) && mUserId == other.mUserId; } @Override public @NonNull String toString() { - return mId; + return mId + "," + mUserId; } @Override public int hashCode() { - return mId.hashCode(); + return Objects.hash(mId, mUserId); } @Override @@ -72,6 +84,7 @@ public final class AppPredictionSessionId implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mId); + dest.writeInt(mUserId); } public static final @android.annotation.NonNull Parcelable.Creator<AppPredictionSessionId> CREATOR = diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java index cd635d635ce1..f0eedf3d18f2 100644 --- a/core/java/android/app/prediction/AppPredictor.java +++ b/core/java/android/app/prediction/AppPredictor.java @@ -96,7 +96,7 @@ public final class AppPredictor { IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE); mPredictionManager = IPredictionManager.Stub.asInterface(b); mSessionId = new AppPredictionSessionId( - context.getPackageName() + ":" + UUID.randomUUID().toString()); + context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId()); try { mPredictionManager.createPredictionSession(predictionContext, mSessionId); } catch (RemoteException e) { diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index d8c653c6d0d5..b672a0857cca 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -643,8 +643,9 @@ public final class BluetoothA2dp implements BluetoothProfile { @SystemApi @Nullable @RequiresPermission(Manifest.permission.BLUETOOTH) - public BluetoothCodecStatus getCodecStatus(@Nullable BluetoothDevice device) { + public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); + verifyDeviceNotNull(device, "getCodecStatus"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { @@ -670,9 +671,14 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public void setCodecConfigPreference(@Nullable BluetoothDevice device, - @Nullable BluetoothCodecConfig codecConfig) { + public void setCodecConfigPreference(@NonNull BluetoothDevice device, + @NonNull BluetoothCodecConfig codecConfig) { if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")"); + verifyDeviceNotNull(device, "setCodecConfigPreference"); + if (codecConfig == null) { + Log.e(TAG, "setCodecConfigPreference: Codec config can't be null"); + throw new IllegalArgumentException("codecConfig cannot be null"); + } try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { @@ -695,8 +701,9 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public void enableOptionalCodecs(@Nullable BluetoothDevice device) { + public void enableOptionalCodecs(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")"); + verifyDeviceNotNull(device, "enableOptionalCodecs"); enableDisableOptionalCodecs(device, true); } @@ -709,8 +716,9 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public void disableOptionalCodecs(@Nullable BluetoothDevice device) { + public void disableOptionalCodecs(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")"); + verifyDeviceNotNull(device, "disableOptionalCodecs"); enableDisableOptionalCodecs(device, false); } @@ -750,7 +758,8 @@ public final class BluetoothA2dp implements BluetoothProfile { @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) @OptionalCodecsSupportStatus - public int supportsOptionalCodecs(@Nullable BluetoothDevice device) { + public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) { + verifyDeviceNotNull(device, "isOptionalCodecsSupported"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -775,7 +784,8 @@ public final class BluetoothA2dp implements BluetoothProfile { @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) @OptionalCodecsPreferenceStatus - public int getOptionalCodecsEnabled(@Nullable BluetoothDevice device) { + public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) { + verifyDeviceNotNull(device, "isOptionalCodecsEnabled"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { @@ -800,8 +810,9 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) - public void setOptionalCodecsEnabled(@Nullable BluetoothDevice device, + public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device, @OptionalCodecsPreferenceStatus int value) { + verifyDeviceNotNull(device, "setOptionalCodecsEnabled"); try { if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED @@ -854,6 +865,13 @@ public final class BluetoothA2dp implements BluetoothProfile { return false; } + private void verifyDeviceNotNull(BluetoothDevice device, String methodName) { + if (device == null) { + Log.e(TAG, methodName + ": device param is null"); + throw new IllegalArgumentException("Device cannot be null"); + } + } + private boolean isValidDevice(BluetoothDevice device) { if (device == null) return false; diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java index ee2cc6d14712..ab492309ba3d 100755 --- a/core/java/android/bluetooth/BluetoothA2dpSink.java +++ b/core/java/android/bluetooth/BluetoothA2dpSink.java @@ -66,7 +66,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { */ @SystemApi @SuppressLint("ActionValue") - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED"; @@ -296,7 +296,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@Nullable BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -345,7 +345,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothA2dpSink service = getService(); @@ -370,7 +370,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isAudioPlaying(@Nullable BluetoothDevice device) { final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 587c92e014c4..66bfcbd27ca6 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1970,6 +1970,38 @@ public final class BluetoothAdapter { } } + private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY = + "cache_key.bluetooth.is_offloaded_filtering_supported"; + private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache = + new PropertyInvalidatedCache<Void, Boolean>( + 8, BLUETOOTH_FILTERING_CACHE_PROPERTY) { + @Override + protected Boolean recompute(Void query) { + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.isOffloadedFilteringSupported(); + } + } catch (RemoteException e) { + Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e); + } finally { + mServiceLock.readLock().unlock(); + } + return false; + + } + }; + + /** @hide */ + public void disableIsOffloadedFilteringSupportedCache() { + mBluetoothFilteringCache.disableLocal(); + } + + /** @hide */ + public static void invalidateIsOffloadedFilteringSupportedCache() { + PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY); + } + /** * Return true if offloaded filters are supported * @@ -1979,17 +2011,7 @@ public final class BluetoothAdapter { if (!getLeAccess()) { return false; } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.isOffloadedFilteringSupported(); - } - } catch (RemoteException e) { - Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e); - } finally { - mServiceLock.readLock().unlock(); - } - return false; + return mBluetoothFilteringCache.query(null); } /** @@ -2361,6 +2383,43 @@ public final class BluetoothAdapter { return BluetoothAdapter.STATE_DISCONNECTED; } + private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY = + "cache_key.bluetooth.get_profile_connection_state"; + private final PropertyInvalidatedCache<Integer, Integer> + mGetProfileConnectionStateCache = + new PropertyInvalidatedCache<Integer, Integer>( + 8, BLUETOOTH_PROFILE_CACHE_PROPERTY) { + @Override + protected Integer recompute(Integer query) { + try { + mServiceLock.readLock().lock(); + if (mService != null) { + return mService.getProfileConnectionState(query); + } + } catch (RemoteException e) { + Log.e(TAG, "getProfileConnectionState:", e); + } finally { + mServiceLock.readLock().unlock(); + } + return BluetoothProfile.STATE_DISCONNECTED; + } + @Override + public String queryToString(Integer query) { + return String.format("getProfileConnectionState(profile=\"%d\")", + query); + } + }; + + /** @hide */ + public void disableGetProfileConnectionStateCache() { + mGetProfileConnectionStateCache.disableLocal(); + } + + /** @hide */ + public static void invalidateGetProfileConnectionStateCache() { + PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY); + } + /** * Get the current connection state of a profile. * This function can be used to check whether the local Bluetooth adapter @@ -2378,17 +2437,7 @@ public final class BluetoothAdapter { if (getState() != STATE_ON) { return BluetoothProfile.STATE_DISCONNECTED; } - try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getProfileConnectionState(profile); - } - } catch (RemoteException e) { - Log.e(TAG, "getProfileConnectionState:", e); - } finally { - mServiceLock.readLock().unlock(); - } - return BluetoothProfile.STATE_DISCONNECTED; + return mGetProfileConnectionStateCache.query(new Integer(profile)); } /** diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index e07ca521e77d..1f89ddf0afc7 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -38,9 +38,6 @@ import java.util.Arrays; import java.util.List; /** - * The Android Bluetooth API is not finalized, and *will* change. Use at your - * own risk. - * * Public API for controlling the Bluetooth Pbap Service. This includes * Bluetooth Phone book Access profile. * BluetoothPbap is a proxy object for controlling the Bluetooth Pbap @@ -56,6 +53,11 @@ import java.util.List; * notification when it is bound, this is especially important if you wish to * immediately call methods on BluetoothPbap after construction. * + * To get an instance of the BluetoothPbap class, you can call + * {@link BluetoothAdapter#getProfileProxy(Context, ServiceListener, int)} with the final param + * being {@link BluetoothProfile#PBAP}. The ServiceListener should be able to get the instance of + * BluetoothPbap in {@link android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected}. + * * Android only supports one connected Bluetooth Pce at a time. * * @hide @@ -87,6 +89,7 @@ public class BluetoothPbap implements BluetoothProfile { */ @SuppressLint("ActionValue") @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; @@ -235,7 +238,8 @@ public class BluetoothPbap implements BluetoothProfile { */ @SystemApi @Override - public int getConnectionState(@Nullable BluetoothDevice device) { + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public @BtProfileState int getConnectionState(@Nullable BluetoothDevice device) { log("getConnectionState: device=" + device); try { final IBluetoothPbap service = mService; @@ -287,7 +291,7 @@ public class BluetoothPbap implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 0e0161ff4e9f..f32a4ab43357 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -700,27 +700,6 @@ public abstract class ContentResolver implements ContentInterface { /** @hide */ public static final String REMOTE_CALLBACK_RESULT = "result"; - /** - * How long we wait for an attached process to publish its content providers - * before we decide it must be hung. - * @hide - */ - public static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS = 10 * 1000; - - /** - * How long we wait for an provider to be published. Should be longer than - * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS}. - * @hide - */ - public static final int CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS = - CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS + 10 * 1000; - - // Should be >= {@link #CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS}, because that's how - // long ActivityManagerService is giving a content provider to get published if a new process - // needs to be started for that. - private static final int GET_TYPE_TIMEOUT_MILLIS = - CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS + 5 * 1000; - public ContentResolver(@Nullable Context context) { this(context, null); } @@ -870,6 +849,8 @@ public abstract class ContentResolver implements ContentInterface { } } + private static final int GET_TYPE_TIMEOUT_MILLIS = 3000; + private static class GetTypeResultListener implements RemoteCallback.OnResultListener { @GuardedBy("this") public boolean done; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6f8a99fce897..0f88c9040d71 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4289,7 +4289,9 @@ public class Intent implements Parcelable, Cloneable { * intent filter in their manifests, so that they can be looked up and bound to by * {@code DataLoaderManagerService}. * - * Data loader service providers must be privileged apps. + * <p class="note">This is a protected intent that can only be sent by the system. + * + * Data loader service providers must be privileged apps. * See {@link com.android.server.pm.PackageManagerShellCommandDataLoader} as an example of such * data loader service provider. * @@ -4970,11 +4972,14 @@ public class Intent implements Parcelable, Cloneable { * <pre> * <accessibility-shortcut-target * android:description="@string/shortcut_target_description" - * android:summary="@string/shortcut_target_summary" /> + * android:summary="@string/shortcut_target_summary" + * android:animatedImageDrawable="@drawable/shortcut_target_animated_image" + * android:htmlDescription="@string/shortcut_target_html_description" /> * </pre> * <p> * Both description and summary are necessary. The system will ignore the accessibility - * shortcut target if they are missing. + * shortcut target if they are missing. The animated image and html description are supported + * to help users understand how to use the shortcut target. * </p> */ @SdkConstant(SdkConstantType.INTENT_CATEGORY) diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java index cd5117be6123..4be7e6df46e0 100644 --- a/core/java/android/content/integrity/AppInstallMetadata.java +++ b/core/java/android/content/integrity/AppInstallMetadata.java @@ -18,7 +18,9 @@ package android.content.integrity; import android.annotation.NonNull; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -40,6 +42,7 @@ public final class AppInstallMetadata { private final List<String> mInstallerCertificates; private final long mVersionCode; private final boolean mIsPreInstalled; + private final Map<String, String> mAllowedInstallersAndCertificates; private AppInstallMetadata(Builder builder) { this.mPackageName = builder.mPackageName; @@ -48,6 +51,7 @@ public final class AppInstallMetadata { this.mInstallerCertificates = builder.mInstallerCertificates; this.mVersionCode = builder.mVersionCode; this.mIsPreInstalled = builder.mIsPreInstalled; + this.mAllowedInstallersAndCertificates = builder.mAllowedInstallersAndCertificates; } @NonNull @@ -80,6 +84,13 @@ public final class AppInstallMetadata { return mIsPreInstalled; } + /** + * Get the allowed installers and their corresponding cert. + */ + public Map<String, String> getAllowedInstallersAndCertificates() { + return mAllowedInstallersAndCertificates; + } + @Override public String toString() { return String.format( @@ -101,6 +112,23 @@ public final class AppInstallMetadata { private List<String> mInstallerCertificates; private long mVersionCode; private boolean mIsPreInstalled; + private Map<String, String> mAllowedInstallersAndCertificates; + + public Builder() { + mAllowedInstallersAndCertificates = new HashMap<>(); + } + + /** + * Add allowed installers and cert. + * + * @see AppInstallMetadata#getAllowedInstallersAndCertificates() + */ + @NonNull + public Builder setAllowedInstallersAndCert( + @NonNull Map<String, String> allowedInstallersAndCertificates) { + this.mAllowedInstallersAndCertificates = allowedInstallersAndCertificates; + return this; + } /** * Set package name of the app to be installed. diff --git a/core/java/android/content/integrity/AtomicFormula.java b/core/java/android/content/integrity/AtomicFormula.java index 42459779e212..d911eabc3b83 100644 --- a/core/java/android/content/integrity/AtomicFormula.java +++ b/core/java/android/content/integrity/AtomicFormula.java @@ -55,14 +55,12 @@ public abstract class AtomicFormula extends IntegrityFormula { PRE_INSTALLED, }) @Retention(RetentionPolicy.SOURCE) - public @interface Key { - } + public @interface Key {} /** @hide */ @IntDef(value = {EQ, GT, GTE}) @Retention(RetentionPolicy.SOURCE) - public @interface Operator { - } + public @interface Operator {} /** * Package name of the app. @@ -354,7 +352,8 @@ public abstract class AtomicFormula extends IntegrityFormula { "Key %s cannot be used with StringAtomicFormula", keyToString(key))); mValue = hashValue(key, value); mIsHashedValue = - key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE + key == APP_CERTIFICATE + || key == INSTALLER_CERTIFICATE ? true : !mValue.equals(value); } diff --git a/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java new file mode 100644 index 000000000000..475f019e7b26 --- /dev/null +++ b/core/java/android/content/integrity/InstallerAllowedByManifestFormula.java @@ -0,0 +1,63 @@ +/* + * 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.content.integrity; + +import java.util.Map; + +/** + * An atomic formula that evaluates to true if the installer of the current install is specified in + * the "allowed installer" field in the android manifest. Note that an empty "allowed installer" by + * default means containing all possible installers. + * + * @hide + */ +public class InstallerAllowedByManifestFormula extends IntegrityFormula { + + @Override + public int getTag() { + return IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG; + } + + @Override + public boolean matches(AppInstallMetadata appInstallMetadata) { + Map<String, String> allowedInstallersAndCertificates = + appInstallMetadata.getAllowedInstallersAndCertificates(); + return allowedInstallersAndCertificates.isEmpty() + || installerInAllowedInstallersFromManifest( + appInstallMetadata, allowedInstallersAndCertificates); + } + + @Override + public boolean isAppCertificateFormula() { + return false; + } + + @Override + public boolean isInstallerFormula() { + return true; + } + + private static boolean installerInAllowedInstallersFromManifest( + AppInstallMetadata appInstallMetadata, + Map<String, String> allowedInstallersAndCertificates) { + return allowedInstallersAndCertificates.containsKey(appInstallMetadata.getInstallerName()) + && appInstallMetadata.getInstallerCertificates() + .contains( + allowedInstallersAndCertificates + .get(appInstallMetadata.getInstallerName())); + } +} diff --git a/core/java/android/content/integrity/IntegrityFormula.java b/core/java/android/content/integrity/IntegrityFormula.java index a2d937e4df31..ac4c9071f755 100644 --- a/core/java/android/content/integrity/IntegrityFormula.java +++ b/core/java/android/content/integrity/IntegrityFormula.java @@ -42,66 +42,88 @@ import java.util.Arrays; @VisibleForTesting public abstract class IntegrityFormula { - /** - * A static formula base for package name formulas. - * - * This formulation is incomplete and should always be used with {@code equals} formulation. - * Evaluates to false when used directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula PACKAGE_NAME = - new StringAtomicFormula(AtomicFormula.PACKAGE_NAME); + /** Factory class for creating integrity formulas based on the app being installed. */ + public static final class Application { + /** Returns an integrity formula that checks the equality to a package name. */ + @NonNull + public static IntegrityFormula packageNameEquals(@NonNull String packageName) { + return new StringAtomicFormula(AtomicFormula.PACKAGE_NAME, packageName); + } - /** - * A static formula base for app certificate formulas. - * - * This formulation is incomplete and should always be used with {@code equals} formulation. - * Evaluates to false when used directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula APP_CERTIFICATE = - new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE); + /** + * Returns an integrity formula that checks if the app certificates contain {@code + * appCertificate}. + */ + @NonNull + public static IntegrityFormula certificatesContain(@NonNull String appCertificate) { + return new StringAtomicFormula(AtomicFormula.APP_CERTIFICATE, appCertificate); + } - /** - * A static formula base for installer name formulas. - * - * This formulation is incomplete and should always be used with {@code equals} formulation. - * Evaluates to false when used directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula INSTALLER_NAME = - new StringAtomicFormula(AtomicFormula.INSTALLER_NAME); + /** Returns an integrity formula that checks the equality to a version code. */ + @NonNull + public static IntegrityFormula versionCodeEquals(@NonNull long versionCode) { + return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode); + } - /** - * A static formula base for installer certificate formulas. - * - * This formulation is incomplete and should always be used with {@code equals} formulation. - * Evaluates to false when used directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula INSTALLER_CERTIFICATE = - new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE); + /** + * Returns an integrity formula that checks the app's version code is greater than the + * provided value. + */ + @NonNull + public static IntegrityFormula versionCodeGreaterThan(@NonNull long versionCode) { + return new LongAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.GT, versionCode); + } - /** - * A static formula base for version code name formulas. - * - * This formulation is incomplete and should always be used with {@code equals}, - * {@code greaterThan} and {@code greaterThanEquals} formulation. Evaluates to false when used - * directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula VERSION_CODE = - new LongAtomicFormula(AtomicFormula.VERSION_CODE); + /** + * Returns an integrity formula that checks the app's version code is greater than or equal + * to the provided value. + */ + @NonNull + public static IntegrityFormula versionCodeGreaterThanOrEqualTo(@NonNull long versionCode) { + return new LongAtomicFormula( + AtomicFormula.VERSION_CODE, AtomicFormula.GTE, versionCode); + } - /** - * A static formula base for pre-installed status formulas. - * - * This formulation is incomplete and should always be used with {@code equals} formulation. - * Evaluates to false when used directly and cannot be written as a parcel. - */ - @NonNull - public static final IntegrityFormula PRE_INSTALLED = - new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED); + /** Returns an integrity formula that is valid when app is pre-installed. */ + @NonNull + public static IntegrityFormula isPreInstalled() { + return new BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true); + } + + private Application() { + } + } + + /** Factory class for creating integrity formulas based on installer. */ + public static final class Installer { + /** Returns an integrity formula that checks the equality to an installer name. */ + @NonNull + public static IntegrityFormula packageNameEquals(@NonNull String installerName) { + return new StringAtomicFormula(AtomicFormula.INSTALLER_NAME, installerName); + } + + /** + * An static formula that evaluates to true if the installer is NOT allowed according to the + * "allowed installer" field in the android manifest. + */ + @NonNull + public static IntegrityFormula notAllowedByManifest() { + return not(new InstallerAllowedByManifestFormula()); + } + + /** + * Returns an integrity formula that checks if the installer certificates contain {@code + * installerCertificate}. + */ + @NonNull + public static IntegrityFormula certificatesContain(@NonNull String installerCertificate) { + return new StringAtomicFormula(AtomicFormula.INSTALLER_CERTIFICATE, + installerCertificate); + } + + private Installer() { + } + } /** @hide */ @IntDef( @@ -109,10 +131,12 @@ public abstract class IntegrityFormula { COMPOUND_FORMULA_TAG, STRING_ATOMIC_FORMULA_TAG, LONG_ATOMIC_FORMULA_TAG, - BOOLEAN_ATOMIC_FORMULA_TAG + BOOLEAN_ATOMIC_FORMULA_TAG, + INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG }) @Retention(RetentionPolicy.SOURCE) - @interface Tag {} + @interface Tag { + } /** @hide */ public static final int COMPOUND_FORMULA_TAG = 0; @@ -122,6 +146,8 @@ public abstract class IntegrityFormula { public static final int LONG_ATOMIC_FORMULA_TAG = 2; /** @hide */ public static final int BOOLEAN_ATOMIC_FORMULA_TAG = 3; + /** @hide */ + public static final int INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG = 4; /** * Returns the tag that identifies the current class. @@ -135,14 +161,14 @@ public abstract class IntegrityFormula { * * @hide */ - public abstract @Tag boolean matches(AppInstallMetadata appInstallMetadata); + public abstract boolean matches(AppInstallMetadata appInstallMetadata); /** * Returns true when the formula (or one of its atomic formulas) has app certificate as key. * * @hide */ - public abstract @Tag boolean isAppCertificateFormula(); + public abstract boolean isAppCertificateFormula(); /** * Returns true when the formula (or one of its atomic formulas) has installer package name @@ -150,7 +176,7 @@ public abstract class IntegrityFormula { * * @hide */ - public abstract @Tag boolean isInstallerFormula(); + public abstract boolean isInstallerFormula(); /** * Write an {@link IntegrityFormula} to {@link android.os.Parcel}. @@ -159,7 +185,6 @@ public abstract class IntegrityFormula { * {@link Parcelable}. * * @throws IllegalArgumentException if {@link IntegrityFormula} is not a recognized subclass - * * @hide */ public static void writeToParcel( @@ -195,70 +220,6 @@ public abstract class IntegrityFormula { } /** - * Returns an integrity formula that evaluates to true when value of the key matches to the - * provided string value. - * - * <p>The value will be hashed with SHA256 and the hex digest will be computed; for - * all cases except when the key is PACKAGE_NAME or INSTALLER_NAME and the value is less than - * 32 characters. - * - * <p>Throws an {@link IllegalArgumentException} if the key is not string typed. - */ - @NonNull - public IntegrityFormula equalTo(@NonNull String value) { - AtomicFormula baseFormula = (AtomicFormula) this; - return new AtomicFormula.StringAtomicFormula(baseFormula.getKey(), value); - } - - /** - * Returns an integrity formula that evaluates to true when the boolean value of the key matches - * the provided boolean value. It can only be used with the boolean comparison keys. - * - * <p>Throws an {@link IllegalArgumentException} if the key is not boolean typed. - */ - @NonNull - public IntegrityFormula equalTo(boolean value) { - AtomicFormula baseFormula = (AtomicFormula) this; - return new AtomicFormula.BooleanAtomicFormula(baseFormula.getKey(), value); - } - - /** - * Returns a formula that evaluates to true when the value of the key in the package being - * installed is equal to {@code value}. - * - * <p>Throws an {@link IllegalArgumentException} if the key is not long typed. - */ - @NonNull - public IntegrityFormula equalTo(long value) { - AtomicFormula baseFormula = (AtomicFormula) this; - return new AtomicFormula.LongAtomicFormula(baseFormula.getKey(), AtomicFormula.EQ, value); - } - - /** - * Returns a formula that evaluates to true when the value of the key in the package being - * installed is greater than {@code value}. - * - * <p>Throws an {@link IllegalArgumentException} if the key is not long typed. - */ - @NonNull - public IntegrityFormula greaterThan(long value) { - AtomicFormula baseFormula = (AtomicFormula) this; - return new AtomicFormula.LongAtomicFormula(baseFormula.getKey(), AtomicFormula.GT, value); - } - - /** - * Returns a formula that evaluates to true when the value of the key in the package being - * installed is greater than or equals to the {@code value}. - * - * <p>Throws an {@link IllegalArgumentException} if the key is not long typed. - */ - @NonNull - public IntegrityFormula greaterThanOrEquals(long value) { - AtomicFormula baseFormula = (AtomicFormula) this; - return new AtomicFormula.LongAtomicFormula(baseFormula.getKey(), AtomicFormula.GTE, value); - } - - /** * Returns a formula that evaluates to true when any formula in {@code formulae} evaluates to * true. * diff --git a/core/java/android/content/pm/InstallationFile.java b/core/java/android/content/pm/InstallationFile.java index 111ad32d1e41..b449945628d2 100644 --- a/core/java/android/content/pm/InstallationFile.java +++ b/core/java/android/content/pm/InstallationFile.java @@ -16,82 +16,59 @@ package android.content.pm; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - /** * Defines the properties of a file in an installation session. - * TODO(b/136132412): update with new APIs. - * * @hide */ @SystemApi public final class InstallationFile implements Parcelable { - public static final int FILE_TYPE_UNKNOWN = -1; - public static final int FILE_TYPE_APK = 0; - public static final int FILE_TYPE_LIB = 1; - public static final int FILE_TYPE_OBB = 2; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"FILE_TYPE_"}, value = { - FILE_TYPE_APK, - FILE_TYPE_LIB, - FILE_TYPE_OBB, - }) - public @interface FileType { - } - - private String mFileName; - private @FileType int mFileType; - private long mFileSize; - private byte[] mMetadata; - - public InstallationFile(@NonNull String fileName, long fileSize, - @Nullable byte[] metadata) { - mFileName = fileName; - mFileSize = fileSize; + private final @PackageInstaller.FileLocation int mLocation; + private final @NonNull String mName; + private final long mLengthBytes; + private final @Nullable byte[] mMetadata; + private final @Nullable byte[] mSignature; + + public InstallationFile(@PackageInstaller.FileLocation int location, @NonNull String name, + long lengthBytes, @Nullable byte[] metadata, @Nullable byte[] signature) { + mLocation = location; + mName = name; + mLengthBytes = lengthBytes; mMetadata = metadata; - if (fileName.toLowerCase().endsWith(".apk")) { - mFileType = FILE_TYPE_APK; - } else if (fileName.toLowerCase().endsWith(".obb")) { - mFileType = FILE_TYPE_OBB; - } else if (fileName.toLowerCase().endsWith(".so") && fileName.toLowerCase().startsWith( - "lib/")) { - mFileType = FILE_TYPE_LIB; - } else { - mFileType = FILE_TYPE_UNKNOWN; - } + mSignature = signature; } - public @FileType int getFileType() { - return mFileType; + public @PackageInstaller.FileLocation int getLocation() { + return mLocation; } public @NonNull String getName() { - return mFileName; + return mName; } - public long getSize() { - return mFileSize; + public long getLengthBytes() { + return mLengthBytes; } public @Nullable byte[] getMetadata() { return mMetadata; } + public @Nullable byte[] getSignature() { + return mSignature; + } + private InstallationFile(Parcel source) { - mFileName = source.readString(); - mFileType = source.readInt(); - mFileSize = source.readLong(); + mLocation = source.readInt(); + mName = source.readString(); + mLengthBytes = source.readLong(); mMetadata = source.createByteArray(); + mSignature = source.createByteArray(); } @Override @@ -101,10 +78,11 @@ public final class InstallationFile implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(mFileName); - dest.writeInt(mFileType); - dest.writeLong(mFileSize); + dest.writeInt(mLocation); + dest.writeString(mName); + dest.writeLong(mLengthBytes); dest.writeByteArray(mMetadata); + dest.writeByteArray(mSignature); } public static final @NonNull Creator<InstallationFile> CREATOR = diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index d2532783f47c..70603b472551 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -16,6 +16,7 @@ package android.content.pm; +import static android.Manifest.permission; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; @@ -601,11 +602,15 @@ public class LauncherApps { } /** - * Show an error log on logcat, when the calling user is a managed profile, and the target - * user is different from the calling user, in order to help developers to detect it. + * Show an error log on logcat, when the calling user is a managed profile, the target + * user is different from the calling user, and it is not called from a package that has the + * {@link permission.INTERACT_ACROSS_USERS_FULL} permission, in order to help + * developers to detect it. */ private void logErrorForInvalidProfileAccess(@NonNull UserHandle target) { - if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile()) { + if (UserHandle.myUserId() != target.getIdentifier() && mUserManager.isManagedProfile() + && mContext.checkSelfPermission(permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "Accessing other profiles/users from managed profile is no longer allowed."); } } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 8c358cc522d6..6a9e0aa047d1 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -45,6 +45,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.UiModeManager; import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; import android.content.LocaleProto; @@ -1975,6 +1976,15 @@ public final class Configuration implements Parcelable, Comparable<Configuration readFromParcel(source); } + + /** + * Retuns whether the configuration is in night mode + * @return true if night mode is active and false otherwise + */ + public boolean isNightModeActive() { + return (uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES; + } + public int compareTo(Configuration that) { int n; float a = this.fontScale; diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 5a1365169eee..add67aa436c6 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -132,6 +132,14 @@ public interface BiometricConstants { int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; /** + * A security vulnerability has been discovered and the sensor is unavailable until a + * security update has addressed this issue. This error can be received if for example, + * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the + * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}. + */ + int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; + + /** * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused * because the authentication attempt was unsuccessful. * @hide diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java index bae0fd3ad3b9..eafcf529de62 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.app.KeyguardManager; +import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.face.FaceManager; /** @@ -36,12 +37,12 @@ public interface BiometricFaceConstants { * authentication. Note this is to accommodate people who have limited * vision. */ - public static final int FEATURE_REQUIRE_ATTENTION = 1; + int FEATURE_REQUIRE_ATTENTION = 1; /** * Require a diverse set of poses during enrollment. Note this is to * accommodate people with limited mobility. */ - public static final int FEATURE_REQUIRE_REQUIRE_DIVERSITY = 2; + int FEATURE_REQUIRE_REQUIRE_DIVERSITY = 2; // // Error messages from face authentication hardware during initialization, enrollment, @@ -50,32 +51,32 @@ public interface BiometricFaceConstants { /** * The hardware is unavailable. Try again later. */ - public static final int FACE_ERROR_HW_UNAVAILABLE = 1; + int FACE_ERROR_HW_UNAVAILABLE = 1; /** * Error state returned when the sensor was unable to process the current image. */ - public static final int FACE_ERROR_UNABLE_TO_PROCESS = 2; + int FACE_ERROR_UNABLE_TO_PROCESS = 2; /** * Error state returned when the current request has been running too long. This is intended to * prevent programs from waiting for the face authentication sensor indefinitely. The timeout is * platform and sensor-specific, but is generally on the order of 30 seconds. */ - public static final int FACE_ERROR_TIMEOUT = 3; + int FACE_ERROR_TIMEOUT = 3; /** * Error state returned for operations like enrollment; the operation cannot be completed * because there's not enough storage remaining to complete the operation. */ - public static final int FACE_ERROR_NO_SPACE = 4; + int FACE_ERROR_NO_SPACE = 4; /** * The operation was canceled because the face authentication sensor is unavailable. For * example, this may happen when the user is switched, the device is locked or another pending * operation prevents or disables it. */ - public static final int FACE_ERROR_CANCELED = 5; + int FACE_ERROR_CANCELED = 5; /** * The {@link FaceManager#remove} call failed. Typically this will happen when the @@ -83,13 +84,13 @@ public interface BiometricFaceConstants { * * @hide */ - public static final int FACE_ERROR_UNABLE_TO_REMOVE = 6; + int FACE_ERROR_UNABLE_TO_REMOVE = 6; /** * The operation was canceled because the API is locked out due to too many attempts. * This occurs after 5 failed attempts, and lasts for 30 seconds. */ - public static final int FACE_ERROR_LOCKOUT = 7; + int FACE_ERROR_LOCKOUT = 7; /** * Hardware vendors may extend this list if there are conditions that do not fall under one of @@ -99,52 +100,62 @@ public interface BiometricFaceConstants { * expected to show the error message string if they happen, but are advised not to rely on the * message id since they will be device and vendor-specific */ - public static final int FACE_ERROR_VENDOR = 8; + int FACE_ERROR_VENDOR = 8; /** * The operation was canceled because FACE_ERROR_LOCKOUT occurred too many times. * Face authentication is disabled until the user unlocks with strong authentication * (PIN/Pattern/Password) */ - public static final int FACE_ERROR_LOCKOUT_PERMANENT = 9; + int FACE_ERROR_LOCKOUT_PERMANENT = 9; /** * The user canceled the operation. Upon receiving this, applications should use alternate * authentication (e.g. a password). The application should also provide the means to return * to face authentication, such as a "use face authentication" button. */ - public static final int FACE_ERROR_USER_CANCELED = 10; + int FACE_ERROR_USER_CANCELED = 10; /** * The user does not have a face enrolled. */ - public static final int FACE_ERROR_NOT_ENROLLED = 11; + int FACE_ERROR_NOT_ENROLLED = 11; /** * The device does not have a face sensor. This message will propagate if the calling app * ignores the result from PackageManager.hasFeature(FEATURE_FACE) and calls * this API anyway. Apps should always check for the feature before calling this API. */ - public static final int FACE_ERROR_HW_NOT_PRESENT = 12; + int FACE_ERROR_HW_NOT_PRESENT = 12; /** * The user pressed the negative button. This is a placeholder that is currently only used * by the support library. + * * @hide */ - public static final int FACE_ERROR_NEGATIVE_BUTTON = 13; + int FACE_ERROR_NEGATIVE_BUTTON = 13; /** * The device does not have pin, pattern, or password set up. See * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} and * {@link KeyguardManager#isDeviceSecure()} */ - public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; + int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; + + /** + * A security vulnerability has been discovered and the sensor is unavailable until a + * security update has addressed this issue. This error can be received if for example, + * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the + * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}. + * @hide + */ + int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; /** * @hide */ - public static final int FACE_ERROR_VENDOR_BASE = 1000; + int FACE_ERROR_VENDOR_BASE = 1000; // // Image acquisition messages. These will not be sent to the user, since they conflict with @@ -154,13 +165,13 @@ public interface BiometricFaceConstants { /** * The image acquired was good. */ - public static final int FACE_ACQUIRED_GOOD = 0; + int FACE_ACQUIRED_GOOD = 0; /** * The face image was not good enough to process due to a detected condition. * (See {@link #FACE_ACQUIRED_TOO_BRIGHT or @link #FACE_ACQUIRED_TOO_DARK}). */ - public static final int FACE_ACQUIRED_INSUFFICIENT = 1; + int FACE_ACQUIRED_INSUFFICIENT = 1; /** * The face image was too bright due to too much ambient light. @@ -169,7 +180,7 @@ public interface BiometricFaceConstants { * The user is expected to take action to retry in better lighting conditions * when this is returned. */ - public static final int FACE_ACQUIRED_TOO_BRIGHT = 2; + int FACE_ACQUIRED_TOO_BRIGHT = 2; /** * The face image was too dark due to illumination light obscured. @@ -178,65 +189,65 @@ public interface BiometricFaceConstants { * The user is expected to take action to retry in better lighting conditions * when this is returned. */ - public static final int FACE_ACQUIRED_TOO_DARK = 3; + int FACE_ACQUIRED_TOO_DARK = 3; /** * The detected face is too close to the sensor, and the image can't be processed. * The user should be informed to move farther from the sensor when this is returned. */ - public static final int FACE_ACQUIRED_TOO_CLOSE = 4; + int FACE_ACQUIRED_TOO_CLOSE = 4; /** * The detected face is too small, as the user might be too far from the sensor. * The user should be informed to move closer to the sensor when this is returned. */ - public static final int FACE_ACQUIRED_TOO_FAR = 5; + int FACE_ACQUIRED_TOO_FAR = 5; /** * Only the upper part of the face was detected. The sensor field of view is too high. * The user should be informed to move up with respect to the sensor when this is returned. */ - public static final int FACE_ACQUIRED_TOO_HIGH = 6; + int FACE_ACQUIRED_TOO_HIGH = 6; /** * Only the lower part of the face was detected. The sensor field of view is too low. * The user should be informed to move down with respect to the sensor when this is returned. */ - public static final int FACE_ACQUIRED_TOO_LOW = 7; + int FACE_ACQUIRED_TOO_LOW = 7; /** * Only the right part of the face was detected. The sensor field of view is too far right. * The user should be informed to move to the right with respect to the sensor * when this is returned. */ - public static final int FACE_ACQUIRED_TOO_RIGHT = 8; + int FACE_ACQUIRED_TOO_RIGHT = 8; /** * Only the left part of the face was detected. The sensor field of view is too far left. * The user should be informed to move to the left with respect to the sensor * when this is returned. */ - public static final int FACE_ACQUIRED_TOO_LEFT = 9; + int FACE_ACQUIRED_TOO_LEFT = 9; /** * The user's eyes have strayed away from the sensor. If this message is sent, the user should * be informed to look at the device. If the user can't be found in the frame, one of the other * acquisition messages should be sent, e.g. FACE_ACQUIRED_NOT_DETECTED. */ - public static final int FACE_ACQUIRED_POOR_GAZE = 10; + int FACE_ACQUIRED_POOR_GAZE = 10; /** * No face was detected in front of the sensor. * The user should be informed to point the sensor to a face when this is returned. */ - public static final int FACE_ACQUIRED_NOT_DETECTED = 11; + int FACE_ACQUIRED_NOT_DETECTED = 11; /** * Too much motion was detected. * The user should be informed to keep their face steady relative to the * sensor. */ - public static final int FACE_ACQUIRED_TOO_MUCH_MOTION = 12; + int FACE_ACQUIRED_TOO_MUCH_MOTION = 12; /** * The sensor needs to be re-calibrated. This is an unexpected condition, and should only be @@ -244,20 +255,20 @@ public interface BiometricFaceConstants { * requires user intervention, e.g. re-enrolling. The expected response to this message is to * direct the user to re-enroll. */ - public static final int FACE_ACQUIRED_RECALIBRATE = 13; + int FACE_ACQUIRED_RECALIBRATE = 13; /** * The face is too different from a previous acquisition. This condition * only applies to enrollment. This can happen if the user passes the * device to someone else in the middle of enrollment. */ - public static final int FACE_ACQUIRED_TOO_DIFFERENT = 14; + int FACE_ACQUIRED_TOO_DIFFERENT = 14; /** * The face is too similar to a previous acquisition. This condition only * applies to enrollment. The user should change their pose. */ - public static final int FACE_ACQUIRED_TOO_SIMILAR = 15; + int FACE_ACQUIRED_TOO_SIMILAR = 15; /** * The magnitude of the pan angle of the user’s face with respect to the sensor’s @@ -269,7 +280,7 @@ public interface BiometricFaceConstants { * * The user should be informed to look more directly at the camera. */ - public static final int FACE_ACQUIRED_PAN_TOO_EXTREME = 16; + int FACE_ACQUIRED_PAN_TOO_EXTREME = 16; /** * The magnitude of the tilt angle of the user’s face with respect to the sensor’s @@ -280,7 +291,7 @@ public interface BiometricFaceConstants { * * The user should be informed to look more directly at the camera. */ - public static final int FACE_ACQUIRED_TILT_TOO_EXTREME = 17; + int FACE_ACQUIRED_TILT_TOO_EXTREME = 17; /** * The magnitude of the roll angle of the user’s face with respect to the sensor’s @@ -292,7 +303,7 @@ public interface BiometricFaceConstants { * * The user should be informed to look more directly at the camera. */ - public static final int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18; + int FACE_ACQUIRED_ROLL_TOO_EXTREME = 18; /** * The user’s face has been obscured by some object. @@ -300,7 +311,7 @@ public interface BiometricFaceConstants { * The user should be informed to remove any objects from the line of sight from * the sensor to the user’s face. */ - public static final int FACE_ACQUIRED_FACE_OBSCURED = 19; + int FACE_ACQUIRED_FACE_OBSCURED = 19; /** * This message represents the earliest message sent at the beginning of the authentication @@ -310,12 +321,12 @@ public interface BiometricFaceConstants { * The framework will measure latency based on the time between the last START message and the * onAuthenticated callback. */ - public static final int FACE_ACQUIRED_START = 20; + int FACE_ACQUIRED_START = 20; /** * The sensor is dirty. The user should be informed to clean the sensor. */ - public static final int FACE_ACQUIRED_SENSOR_DIRTY = 21; + int FACE_ACQUIRED_SENSOR_DIRTY = 21; /** * Hardware vendors may extend this list if there are conditions that do not fall under one of @@ -323,10 +334,10 @@ public interface BiometricFaceConstants { * * @hide */ - public static final int FACE_ACQUIRED_VENDOR = 22; + int FACE_ACQUIRED_VENDOR = 22; /** * @hide */ - public static final int FACE_ACQUIRED_VENDOR_BASE = 1000; + int FACE_ACQUIRED_VENDOR_BASE = 1000; } diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java index 5c74456eb60a..46e8cc036809 100644 --- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java @@ -18,6 +18,7 @@ package android.hardware.biometrics; import android.app.KeyguardManager; import android.compat.annotation.UnsupportedAppUsage; +import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.fingerprint.FingerprintManager; /** @@ -37,32 +38,32 @@ public interface BiometricFingerprintConstants { /** * The hardware is unavailable. Try again later. */ - public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; + int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; /** * Error state returned when the sensor was unable to process the current image. */ - public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; + int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; /** * Error state returned when the current request has been running too long. This is intended to * prevent programs from waiting for the fingerprint sensor indefinitely. The timeout is * platform and sensor-specific, but is generally on the order of 30 seconds. */ - public static final int FINGERPRINT_ERROR_TIMEOUT = 3; + int FINGERPRINT_ERROR_TIMEOUT = 3; /** * Error state returned for operations like enrollment; the operation cannot be completed * because there's not enough storage remaining to complete the operation. */ - public static final int FINGERPRINT_ERROR_NO_SPACE = 4; + int FINGERPRINT_ERROR_NO_SPACE = 4; /** * The operation was canceled because the fingerprint sensor is unavailable. For example, * this may happen when the user is switched, the device is locked or another pending operation * prevents or disables it. */ - public static final int FINGERPRINT_ERROR_CANCELED = 5; + int FINGERPRINT_ERROR_CANCELED = 5; /** * The {@link FingerprintManager#remove} call failed. Typically this will happen when the @@ -70,13 +71,13 @@ public interface BiometricFingerprintConstants { * * @hide */ - public static final int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; + int FINGERPRINT_ERROR_UNABLE_TO_REMOVE = 6; /** * The operation was canceled because the API is locked out due to too many attempts. * This occurs after 5 failed attempts, and lasts for 30 seconds. */ - public static final int FINGERPRINT_ERROR_LOCKOUT = 7; + int FINGERPRINT_ERROR_LOCKOUT = 7; /** * Hardware vendors may extend this list if there are conditions that do not fall under one of @@ -86,52 +87,63 @@ public interface BiometricFingerprintConstants { * expected to show the error message string if they happen, but are advised not to rely on the * message id since they will be device and vendor-specific */ - public static final int FINGERPRINT_ERROR_VENDOR = 8; + int FINGERPRINT_ERROR_VENDOR = 8; /** * The operation was canceled because FINGERPRINT_ERROR_LOCKOUT occurred too many times. * Fingerprint authentication is disabled until the user unlocks with strong authentication * (PIN/Pattern/Password) */ - public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; + int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; /** * The user canceled the operation. Upon receiving this, applications should use alternate * authentication (e.g. a password). The application should also provide the means to return * to fingerprint authentication, such as a "use fingerprint" button. */ - public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; + int FINGERPRINT_ERROR_USER_CANCELED = 10; /** * The user does not have any fingerprints enrolled. */ - public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; + int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; /** * The device does not have a fingerprint sensor. */ - public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; + int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; /** * The user pressed the negative button. This is a placeholder that is currently only used * by the support library. + * * @hide */ - public static final int FINGERPRINT_ERROR_NEGATIVE_BUTTON = 13; + int FINGERPRINT_ERROR_NEGATIVE_BUTTON = 13; /** * The device does not have pin, pattern, or password set up. See * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} and * {@link KeyguardManager#isDeviceSecure()} + * * @hide */ - public static final int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; + int BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL = 14; + + /** + * A security vulnerability has been discovered and the sensor is unavailable until a + * security update has addressed this issue. This error can be received if for example, + * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the + * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}. + * @hide + */ + public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; /** * @hide */ @UnsupportedAppUsage - public static final int FINGERPRINT_ERROR_VENDOR_BASE = 1000; + int FINGERPRINT_ERROR_VENDOR_BASE = 1000; // // Image acquisition messages. Must agree with those in fingerprint.h @@ -140,19 +152,19 @@ public interface BiometricFingerprintConstants { /** * The image acquired was good. */ - public static final int FINGERPRINT_ACQUIRED_GOOD = 0; + int FINGERPRINT_ACQUIRED_GOOD = 0; /** * Only a partial fingerprint image was detected. During enrollment, the user should be * informed on what needs to happen to resolve this problem, e.g. "press firmly on sensor." */ - public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; + int FINGERPRINT_ACQUIRED_PARTIAL = 1; /** * The fingerprint image was too noisy to process due to a detected condition (i.e. dry skin) or * a possibly dirty sensor (See {@link #FINGERPRINT_ACQUIRED_IMAGER_DIRTY}). */ - public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; + int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; /** * The fingerprint image was too noisy due to suspected or detected dirt on the sensor. @@ -161,13 +173,13 @@ public interface BiometricFingerprintConstants { * (stuck pixels, swaths, etc.). The user is expected to take action to clean the sensor * when this is returned. */ - public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; + int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; /** * The fingerprint image was unreadable due to lack of motion. This is most appropriate for * linear array sensors that require a swipe motion. */ - public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; + int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; /** * The fingerprint image was incomplete due to quick motion. While mostly appropriate for @@ -175,16 +187,29 @@ public interface BiometricFingerprintConstants { * The user should be asked to move the finger slower (linear) or leave the finger on the sensor * longer. */ - public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; + int FINGERPRINT_ACQUIRED_TOO_FAST = 5; /** * Hardware vendors may extend this list if there are conditions that do not fall under one of * the above categories. Vendors are responsible for providing error strings for these errors. + * * @hide */ - public static final int FINGERPRINT_ACQUIRED_VENDOR = 6; + int FINGERPRINT_ACQUIRED_VENDOR = 6; + + /** + * This message represents the earliest message sent at the beginning of the authentication + * pipeline. It is expected to be used to measure latency. Note this should be sent whenever + * authentication is restarted. + * The framework will measure latency based on the time between the last START message and the + * onAuthenticated callback. + * + * @hide + */ + int FINGERPRINT_ACQUIRED_START = 7; + /** * @hide */ - public static final int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; + int FINGERPRINT_ACQUIRED_VENDOR_BASE = 1000; } diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 125b676e14f7..7d66cae4c845 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -61,10 +61,20 @@ public class BiometricManager { public static final int BIOMETRIC_ERROR_NO_HARDWARE = BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT; + /** + * A security vulnerability has been discovered and the sensor is unavailable until a + * security update has addressed this issue. This error can be received if for example, + * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the + * sensor's strength can currently only meet {@link Authenticators#BIOMETRIC_WEAK}. + */ + public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = + BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED; + @IntDef({BIOMETRIC_SUCCESS, BIOMETRIC_ERROR_HW_UNAVAILABLE, BIOMETRIC_ERROR_NONE_ENROLLED, - BIOMETRIC_ERROR_NO_HARDWARE}) + BIOMETRIC_ERROR_NO_HARDWARE, + BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED}) @interface BiometricError {} /** diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index f6717c77f90e..ea576bc569d2 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -918,7 +918,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing if (mEnrollmentCallback != null) { mEnrollmentCallback.onEnrollmentHelp(clientInfo, msg); } else if (mAuthenticationCallback != null) { - mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); + if (acquireInfo != BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START) { + mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg); + } } } @@ -1050,6 +1052,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return msgArray[vendorCode]; } } + break; + case FINGERPRINT_ACQUIRED_START: + return null; } Slog.w(TAG, "Invalid acquired message: " + acquireInfo + ", " + vendorCode); return null; diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java index 987a53e337a0..25fb3e0ed907 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -29,6 +29,8 @@ package android.os.incremental; * @throws IllegalStateException the session is not an Incremental installation session. */ +import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -85,7 +87,7 @@ public final class IncrementalFileStorages { try { result = new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams); for (InstallationFile file : addedFiles) { - if (file.getFileType() == InstallationFile.FILE_TYPE_APK) { + if (file.getLocation() == LOCATION_DATA_APP) { try { result.addApkFile(file); } catch (IOException e) { @@ -95,7 +97,7 @@ public final class IncrementalFileStorages { e); } } else { - throw new IOException("Unknown file type: " + file.getFileType()); + throw new IOException("Unknown file location: " + file.getLocation()); } } @@ -147,8 +149,8 @@ public final class IncrementalFileStorages { String apkName = apk.getName(); File targetFile = Paths.get(stageDirPath, apkName).toFile(); if (!targetFile.exists()) { - mDefaultStorage.makeFile(apkName, apk.getSize(), null, - apk.getMetadata(), 0, null, null, null); + mDefaultStorage.makeFile(apkName, apk.getLengthBytes(), null, apk.getMetadata(), + apk.getSignature()); } } diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index ba38268949b5..d2d8f85b1b35 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -299,7 +299,16 @@ public final class IncrementalManager { return nativeIsIncrementalPath(path); } + /** + * Returns raw signature for file if it's on Incremental File System. + * Unsafe, use only if you are sure what you are doing. + */ + public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) { + return nativeUnsafeGetFileSignature(path); + } + /* Native methods */ private static native boolean nativeIsEnabled(); private static native boolean nativeIsIncrementalPath(@NonNull String path); + private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path); } diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index 5df44ff49059..f4e1f967dca8 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.RemoteException; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; @@ -169,10 +171,11 @@ public final class IncrementalStorage { * @param path Relative path of the new file. * @param size Size of the new file in bytes. * @param metadata Metadata bytes. + * @param v4signatureBytes Serialized V4SignatureProto. */ public void makeFile(@NonNull String path, long size, @Nullable UUID id, - @Nullable byte[] metadata, int hashAlgorithm, @Nullable byte[] rootHash, - @Nullable byte[] additionalData, @Nullable byte[] signature) throws IOException { + @Nullable byte[] metadata, @Nullable byte[] v4signatureBytes) + throws IOException { try { if (id == null && metadata == null) { throw new IOException("File ID and metadata cannot both be null"); @@ -181,13 +184,7 @@ public final class IncrementalStorage { params.size = size; params.metadata = (metadata == null ? new byte[0] : metadata); params.fileId = idToBytes(id); - if (hashAlgorithm != 0 || signature != null) { - params.signature = new IncrementalSignature(); - params.signature.hashAlgorithm = hashAlgorithm; - params.signature.rootHash = rootHash; - params.signature.additionalData = additionalData; - params.signature.signature = signature; - } + params.signature = parseV4Signature(v4signatureBytes); int res = mService.makeFile(mId, path, params); if (res != 0) { throw new IOException("makeFile() failed with errno " + -res); @@ -197,6 +194,7 @@ public final class IncrementalStorage { } } + /** * Creates a file in Incremental storage. The content of the file is mapped from a range inside * a source file in the same storage. @@ -349,6 +347,37 @@ public final class IncrementalStorage { } } + /** + * Returns the metadata object of an IncFs File. + * + * @param id The file id. + * @return Byte array that contains metadata bytes. + */ + @Nullable + public byte[] getFileMetadata(@NonNull UUID id) { + try { + final byte[] rawId = idToBytes(id); + return mService.getMetadataById(mId, rawId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** + * Informs the data loader service associated with the current storage to start data loader + * + * @return True if data loader is successfully started. + */ + public boolean startLoading() { + try { + return mService.startLoading(mId); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return false; + } + } + private static final int UUID_BYTE_SIZE = 16; /** @@ -386,35 +415,44 @@ public final class IncrementalStorage { return new UUID(msb, lsb); } + private static final int INCFS_HASH_SHA256 = 1; + private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256 + private static final int INCFS_MAX_ADD_DATA_SIZE = 128; + /** - * Returns the metadata object of an IncFs File. - * - * @param id The file id. - * @return Byte array that contains metadata bytes. + * Deserialize and validate v4 signature bytes. */ - @Nullable - public byte[] getFileMetadata(@NonNull UUID id) { - try { - final byte[] rawId = idToBytes(id); - return mService.getMetadataById(mId, rawId); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + private static IncrementalSignature parseV4Signature(@Nullable byte[] v4signatureBytes) + throws IOException { + if (v4signatureBytes == null) { return null; } - } - /** - * Informs the data loader service associated with the current storage to start data loader - * - * @return True if data loader is successfully started. - */ - public boolean startLoading() { - try { - return mService.startLoading(mId); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return false; + final V4Signature signature; + try (DataInputStream input = new DataInputStream( + new ByteArrayInputStream(v4signatureBytes))) { + signature = V4Signature.readFrom(input); + } + + final byte[] rootHash = signature.verityRootHash; + final byte[] additionalData = signature.v3Digest; + final byte[] pkcs7Signature = signature.pkcs7SignatureBlock; + + if (rootHash.length != INCFS_MAX_HASH_SIZE) { + throw new IOException("rootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes"); + } + if (additionalData.length > INCFS_MAX_ADD_DATA_SIZE) { + throw new IOException( + "additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes"); } + + IncrementalSignature result = new IncrementalSignature(); + result.hashAlgorithm = INCFS_HASH_SHA256; + result.rootHash = rootHash; + result.additionalData = additionalData; + result.signature = pkcs7Signature; + + return result; } /** diff --git a/core/java/android/os/incremental/V4Signature.java b/core/java/android/os/incremental/V4Signature.java new file mode 100644 index 000000000000..5fadee4355b3 --- /dev/null +++ b/core/java/android/os/incremental/V4Signature.java @@ -0,0 +1,62 @@ +/* + * 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.incremental; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * V4 signature fields. + * Keep in sync with APKSig master copy. + * @hide + */ +public class V4Signature { + public final byte[] verityRootHash; + public final byte[] v3Digest; + public final byte[] pkcs7SignatureBlock; + + V4Signature(byte[] verityRootHash, byte[] v3Digest, byte[] pkcs7SignatureBlock) { + this.verityRootHash = verityRootHash; + this.v3Digest = v3Digest; + this.pkcs7SignatureBlock = pkcs7SignatureBlock; + } + + static byte[] readBytes(DataInputStream stream) throws IOException { + byte[] result = new byte[stream.readInt()]; + stream.read(result); + return result; + } + + static V4Signature readFrom(DataInputStream stream) throws IOException { + byte[] verityRootHash = readBytes(stream); + byte[] v3Digest = readBytes(stream); + byte[] pkcs7SignatureBlock = readBytes(stream); + return new V4Signature(verityRootHash, v3Digest, pkcs7SignatureBlock); + } + + static void writeBytes(DataOutputStream stream, byte[] bytes) throws IOException { + stream.writeInt(bytes.length); + stream.write(bytes); + } + + void writeTo(DataOutputStream stream) throws IOException { + writeBytes(stream, this.verityRootHash); + writeBytes(stream, this.v3Digest); + writeBytes(stream, this.pkcs7SignatureBlock); + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b9207e5a12d5..ea0afa94c8aa 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10207,7 +10207,9 @@ public final class Settings { * * Type: int (0 for false, 1 for true) * @hide + * @deprecated Use {@link WifiManager#isAutoWakeupEnabled()} instead. */ + @Deprecated @SystemApi public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled"; @@ -10243,7 +10245,6 @@ public final class Settings { * enabled state. * @hide */ - @SystemApi public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled"; diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java index c21577842199..b9700b2b80db 100644 --- a/core/java/android/service/dataloader/DataLoaderService.java +++ b/core/java/android/service/dataloader/DataLoaderService.java @@ -90,7 +90,7 @@ public abstract class DataLoaderService extends Service { * @hide */ @SystemApi - public @Nullable DataLoader onCreateDataLoader() { + public @Nullable DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) { return null; } diff --git a/core/java/android/view/AccessibilityEmbeddedConnection.java b/core/java/android/view/AccessibilityEmbeddedConnection.java new file mode 100644 index 000000000000..cc1e5010edc7 --- /dev/null +++ b/core/java/android/view/AccessibilityEmbeddedConnection.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Matrix; +import android.os.IBinder; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityEmbeddedConnection; + +import java.lang.ref.WeakReference; + +/** + * This class is an interface this ViewRootImpl provides to the host view to the latter + * can interact with the view hierarchy in SurfaceControlViewHost. + * + * @hide + */ +final class AccessibilityEmbeddedConnection extends IAccessibilityEmbeddedConnection.Stub { + private final WeakReference<ViewRootImpl> mViewRootImpl; + + AccessibilityEmbeddedConnection(ViewRootImpl viewRootImpl) { + mViewRootImpl = new WeakReference<>(viewRootImpl); + } + + @Override + public @Nullable IBinder associateEmbeddedHierarchy(@NonNull IBinder host, int hostViewId) { + final ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null) { + final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance( + viewRootImpl.mContext); + viewRootImpl.mAttachInfo.mLeashedParentToken = host; + viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = hostViewId; + if (accessibilityManager.isEnabled()) { + accessibilityManager.associateEmbeddedHierarchy(host, viewRootImpl.mLeashToken); + } + return viewRootImpl.mLeashToken; + } + return null; + } + + @Override + public void disassociateEmbeddedHierarchy() { + final ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null) { + final AccessibilityManager accessibilityManager = AccessibilityManager.getInstance( + viewRootImpl.mContext); + viewRootImpl.mAttachInfo.mLeashedParentToken = null; + viewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId = View.NO_ID; + viewRootImpl.mAttachInfo.mLocationInParentDisplay.set(0, 0); + if (accessibilityManager.isEnabled()) { + accessibilityManager.disassociateEmbeddedHierarchy(viewRootImpl.mLeashToken); + } + } + } + + @Override + public void setScreenMatrix(float[] matrixValues) { + final ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null) { + // TODO(b/148821260): Implement the rest of matrix values. + viewRootImpl.mAttachInfo.mLocationInParentDisplay.set( + (int) matrixValues[Matrix.MTRANS_X], (int) matrixValues[Matrix.MTRANS_Y]); + } + } +} diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 203b08765663..3ca84c1eb18d 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -855,6 +855,36 @@ public final class AccessibilityInteractionController { return mViewRootImpl.mAttachInfo.mLocationInParentDisplay.equals(0, 0); } + private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> infos) { + if (infos == null || shouldBypassAssociateLeashedParent()) { + return; + } + final int infoCount = infos.size(); + for (int i = 0; i < infoCount; i++) { + final AccessibilityNodeInfo info = infos.get(i); + associateLeashedParentIfNeeded(info); + } + } + + private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) { + if (info == null || shouldBypassAssociateLeashedParent()) { + return; + } + // The node id of root node in embedded maybe not be ROOT_NODE_ID so we compare the id + // with root view. + if (mViewRootImpl.mView.getAccessibilityViewId() + != AccessibilityNodeInfo.getAccessibilityViewId(info.getSourceNodeId())) { + return; + } + info.setLeashedParent(mViewRootImpl.mAttachInfo.mLeashedParentToken, + mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId); + } + + private boolean shouldBypassAssociateLeashedParent() { + return (mViewRootImpl.mAttachInfo.mLeashedParentToken == null + && mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId == View.NO_ID); + } + private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec) { if (info == null) { @@ -914,6 +944,7 @@ public final class AccessibilityInteractionController { MagnificationSpec spec, Region interactiveRegion) { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + associateLeashedParentIfNeeded(infos); adjustBoundsInScreenIfNeeded(infos); // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, // then impact the visibility result, we need to adjust visibility before apply scale. @@ -935,6 +966,7 @@ public final class AccessibilityInteractionController { MagnificationSpec spec, Region interactiveRegion) { try { mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0; + associateLeashedParentIfNeeded(info); adjustBoundsInScreenIfNeeded(info); // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node, // then impact the visibility result, we need to adjust visibility before apply scale. diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index f9a023fafc3c..17303478fa50 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -97,6 +97,8 @@ interface IWindowManager IWindowSession openSession(in IWindowSessionCallback callback); + boolean useBLAST(); + @UnsupportedAppUsage void getInitialDisplaySize(int displayId, out Point size); @UnsupportedAppUsage diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index d4961eab7473..6caa4fed6409 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -198,7 +198,7 @@ public class InsetsSource implements Parcelable { return "InsetsSource: {" + "mType=" + InsetsState.typeToString(mType) + ", mFrame=" + mFrame.toShortString() - + ", mVisible" + mVisible + + ", mVisible=" + mVisible + "}"; } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 78a080de20e5..4ac6a666a21b 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -258,14 +258,14 @@ public class Surface implements Parcelable { */ public void release() { synchronized (mLock) { - if (mNativeObject != 0) { - nativeRelease(mNativeObject); - setNativeObjectLocked(0); - } if (mHwuiContext != null) { mHwuiContext.destroy(); mHwuiContext = null; } + if (mNativeObject != 0) { + nativeRelease(mNativeObject); + setNativeObjectLocked(0); + } } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index b1c354f6f717..637a088e4c5d 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -22,7 +22,6 @@ import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAY import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.CompatibilityInfo.Translator; @@ -43,8 +42,8 @@ import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceControl.Transaction; -import android.view.accessibility.AccessibilityNodeInfo; import android.view.SurfaceControlViewHost; +import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.view.SurfaceCallbackHelper; @@ -386,7 +385,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * This gets called on a RenderThread worker thread, so members accessed here must * be protected by a lock. */ - final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; + final boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST(); viewRoot.registerRtFrameCallback(frame -> { try { final SurfaceControl.Transaction t = useBLAST ? @@ -1120,7 +1119,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t, Rect position, long frameNumber) { - if (frameNumber > 0 && !WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (frameNumber > 0 && !WindowManagerGlobal.getInstance().useBLAST()) { final ViewRootImpl viewRoot = getViewRootImpl(); t.deferTransactionUntil(surface, viewRoot.getRenderSurfaceControl(), @@ -1138,7 +1137,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private void setParentSpaceRectangle(Rect position, long frameNumber) { - final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; + final boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST(); final ViewRootImpl viewRoot = getViewRootImpl(); final SurfaceControl.Transaction t = useBLAST ? viewRoot.getBLASTSyncTransaction() : mRtTransaction; @@ -1199,7 +1198,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionLost(long frameNumber) { - boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER; + boolean useBLAST = WindowManagerGlobal.getInstance().useBLAST(); if (DEBUG) { Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d", System.identityHashCode(this), frameNumber)); @@ -1538,7 +1537,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void invalidate(boolean invalidateCache) { super.invalidate(invalidateCache); - if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (!WindowManagerGlobal.getInstance().useBLAST()) { return; } final ViewRootImpl viewRoot = getViewRootImpl(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a6f8fad817e1..f99c96584585 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -28989,6 +28989,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, OnContentApplyWindowInsetsListener mContentOnApplyWindowInsetsListener; /** + * The leash token of this view's parent when it's in an embedded hierarchy that is + * re-parented to another window. + */ + IBinder mLeashedParentToken; + + /** + * The accessibility view id of this view's parent when it's in an embedded + * hierarchy that is re-parented to another window. + */ + int mLeashedParentAccessibilityViewId; + + /** * Creates a new set of attachment information with the specified * events handler and thread. * diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 159b93eb12dd..fa4fafaf3b26 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -135,6 +135,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityWindowInfo; +import android.view.accessibility.IAccessibilityEmbeddedConnection; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.animation.AccelerateDecelerateInterpolator; @@ -315,6 +316,8 @@ public final class ViewRootImpl implements ViewParent, */ private boolean mForceNextConfigUpdate; + private final boolean mUseBLASTAdapter; + /** * Signals that compatibility booleans have been initialized according to * target SDK versions. @@ -353,6 +356,8 @@ public final class ViewRootImpl implements ViewParent, final W mWindow; + final IBinder mLeashToken; + final int mTargetSdkVersion; int mSeq; @@ -650,6 +655,8 @@ public final class ViewRootImpl implements ViewParent, private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); + private IAccessibilityEmbeddedConnection mEmbeddedConnection; + static final class SystemUiVisibilityInfo { int seq; int globalVisibility; @@ -683,6 +690,7 @@ public final class ViewRootImpl implements ViewParent, mVisRect = new Rect(); mWinFrame = new Rect(); mWindow = new W(this); + mLeashToken = new Binder(); mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; mViewVisibility = View.GONE; mTransparentRegion = new Region(); @@ -734,6 +742,7 @@ public final class ViewRootImpl implements ViewParent, loadSystemProperties(); mImeFocusController = new ImeFocusController(this); + mUseBLASTAdapter = WindowManagerGlobal.getInstance().useBLAST(); } public static void addFirstDrawHandler(Runnable callback) { @@ -861,7 +870,7 @@ public final class ViewRootImpl implements ViewParent, if (mWindowAttributes.packageName == null) { mWindowAttributes.packageName = mBasePackageName; } - if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (mUseBLASTAdapter) { mWindowAttributes.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; } @@ -1341,7 +1350,7 @@ public final class ViewRootImpl implements ViewParent, } mWindowAttributes.privateFlags |= compatibleWindowFlag; - if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (mUseBLASTAdapter) { mWindowAttributes.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; } @@ -7342,7 +7351,7 @@ public final class ViewRootImpl implements ViewParent, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize, mBlastSurfaceControl); if (mSurfaceControl.isValid()) { - if (!WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (!mUseBLASTAdapter) { mSurface.copyFrom(mSurfaceControl); } else { mSurface.transferFrom(getOrCreateBLASTSurface(mSurfaceSize.x, @@ -9154,6 +9163,10 @@ public final class ViewRootImpl implements ViewParent, focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } } + if (mAttachInfo.mLeashedParentToken != null) { + mAccessibilityManager.associateEmbeddedHierarchy( + mAttachInfo.mLeashedParentToken, mLeashToken); + } } else { ensureNoConnection(); mHandler.obtainMessage(MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST).sendToTarget(); @@ -9166,6 +9179,7 @@ public final class ViewRootImpl implements ViewParent, if (!registered) { mAttachInfo.mAccessibilityWindowId = mAccessibilityManager.addAccessibilityInteractionConnection(mWindow, + mLeashToken, mContext.getPackageName(), new AccessibilityInteractionConnection(ViewRootImpl.this)); } @@ -9352,6 +9366,17 @@ public final class ViewRootImpl implements ViewParent, } } + /** + * Gets an accessibility embedded connection interface for this ViewRootImpl. + * @hide + */ + public IAccessibilityEmbeddedConnection getEmbeddedConnection() { + if (mEmbeddedConnection == null) { + mEmbeddedConnection = new AccessibilityEmbeddedConnection(ViewRootImpl.this); + } + return mEmbeddedConnection; + } + private class SendWindowContentChangedAccessibilityEvent implements Runnable { private int mChangeTypes = 0; @@ -9537,7 +9562,7 @@ public final class ViewRootImpl implements ViewParent, } SurfaceControl getRenderSurfaceControl() { - if (WindowManagerGlobal.USE_BLAST_ADAPTER) { + if (mUseBLASTAdapter) { return mBlastSurfaceControl; } else { return mSurfaceControl; diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index f501de91b3b7..ea8e73885980 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -16,8 +16,6 @@ package android.view; -import static android.view.WindowInsets.Type.ime; - import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -148,7 +146,7 @@ public interface WindowInsetsController { * @param types The {@link InsetsType}s the application has requested to control. * @param durationMillis Duration of animation in * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the - * animation doesn't have a predetermined duration.T his value will be + * animation doesn't have a predetermined duration. This value will be * passed to {@link InsetsAnimation#getDurationMillis()} * @param interpolator The interpolator used for this animation, or {@code null} if this * animation doesn't follow an interpolation curve. This value will be diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index f03c4e731283..c22b8921390c 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -56,13 +56,7 @@ import java.util.ArrayList; public final class WindowManagerGlobal { private static final String TAG = "WindowManager"; - private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter"; - - /** - * This flag controls whether ViewRootImpl will utilize the Blast Adapter - * to send buffer updates to SurfaceFlinger - */ - public static final boolean USE_BLAST_ADAPTER = false; + private final boolean mUseBLASTAdapter; /** * The user is navigating with keys (not the touch screen), so @@ -165,6 +159,11 @@ public final class WindowManagerGlobal { private Runnable mSystemPropertyUpdater; private WindowManagerGlobal() { + try { + mUseBLASTAdapter = getWindowManagerService().useBLAST(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } @UnsupportedAppUsage @@ -233,6 +232,13 @@ public final class WindowManagerGlobal { } } + /** + * Whether or not to use BLAST for ViewRootImpl + */ + public boolean useBLAST() { + return mUseBLASTAdapter; + } + @UnsupportedAppUsage public String[] getViewRootNames() { synchronized (mLock) { diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 02b098b27974..dc87453bd867 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1086,6 +1086,50 @@ public final class AccessibilityManager { } /** + * Associate the connection between the host View and the embedded SurfaceControlViewHost. + * + * @hide + */ + public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.associateEmbeddedHierarchy(host, embedded); + } catch (RemoteException e) { + return; + } + } + + /** + * Disassociate the connection between the host View and the embedded SurfaceControlViewHost. + * The given token could be either from host side or embedded side. + * + * @hide + */ + public void disassociateEmbeddedHierarchy(@NonNull IBinder token) { + if (token == null) { + return; + } + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return; + } + } + try { + service.disassociateEmbeddedHierarchy(token); + } catch (RemoteException e) { + return; + } + } + + /** * Sets the current state and notifies listeners, if necessary. * * @param stateFlags The state flags. @@ -1147,11 +1191,12 @@ public final class AccessibilityManager { /** * Adds an accessibility interaction connection interface for a given window. * @param windowToken The window token to which a connection is added. + * @param leashToken The leash token to which a connection is added. * @param connection The connection. * * @hide */ - public int addAccessibilityInteractionConnection(IWindow windowToken, + public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, String packageName, IAccessibilityInteractionConnection connection) { final IAccessibilityManager service; final int userId; @@ -1163,8 +1208,8 @@ public final class AccessibilityManager { userId = mUserId; } try { - return service.addAccessibilityInteractionConnection(windowToken, connection, - packageName, userId); + return service.addAccessibilityInteractionConnection(windowToken, leashToken, + connection, packageName, userId); } catch (RemoteException re) { Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); } diff --git a/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl b/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl new file mode 100644 index 000000000000..707099ef09f4 --- /dev/null +++ b/core/java/android/view/accessibility/IAccessibilityEmbeddedConnection.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + +/** + * Interface used by host View to talk to the view root of the embedded SurfaceControlViewHost + * that actually implements the functionality. + * + * @hide + */ +interface IAccessibilityEmbeddedConnection { + + IBinder associateEmbeddedHierarchy(IBinder hostToken, int sourceId); + + void disassociateEmbeddedHierarchy(); + + oneway void setScreenMatrix(in float[] matrixValues); +} diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 7f8fdf83995a..97036f3f3a1b 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -47,7 +47,7 @@ interface IAccessibilityManager { @UnsupportedAppUsage List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId); - int addAccessibilityInteractionConnection(IWindow windowToken, + int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, in IAccessibilityInteractionConnection connection, String packageName, int userId); @@ -88,4 +88,8 @@ interface IAccessibilityManager { oneway void registerSystemAction(in RemoteAction action, int actionId); oneway void unregisterSystemAction(int actionId); oneway void setWindowMagnificationConnection(in IWindowMagnificationConnection connection); + + void associateEmbeddedHierarchy(IBinder host, IBinder embedded); + + void disassociateEmbeddedHierarchy(IBinder token); } diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java index 4329a206746d..fed3dbf8f49c 100644 --- a/core/java/android/view/textclassifier/TextClassificationSession.java +++ b/core/java/android/view/textclassifier/TextClassificationSession.java @@ -16,6 +16,7 @@ package android.view.textclassifier; +import android.annotation.NonNull; import android.annotation.WorkerThread; import android.view.textclassifier.SelectionEvent.InvocationMethod; @@ -23,6 +24,8 @@ import com.android.internal.util.Preconditions; import java.util.Objects; +import sun.misc.Cleaner; + /** * Session-aware TextClassifier. */ @@ -35,6 +38,7 @@ final class TextClassificationSession implements TextClassifier { private final SelectionEventHelper mEventHelper; private final TextClassificationSessionId mSessionId; private final TextClassificationContext mClassificationContext; + private final Cleaner mCleaner; private boolean mDestroyed; @@ -44,6 +48,8 @@ final class TextClassificationSession implements TextClassifier { mSessionId = new TextClassificationSessionId(); mEventHelper = new SelectionEventHelper(mSessionId, mClassificationContext); initializeRemoteSession(); + // This ensures destroy() is called if the client forgot to do so. + mCleaner = Cleaner.create(this, new CleanerRunnable(mEventHelper, mDelegate)); } @Override @@ -114,8 +120,7 @@ final class TextClassificationSession implements TextClassifier { @Override public void destroy() { - mEventHelper.endSession(); - mDelegate.destroy(); + mCleaner.clean(); mDestroyed = true; } @@ -258,4 +263,25 @@ final class TextClassificationSession implements TextClassifier { } } } + + // We use a static nested class here to avoid retaining the object reference of the outer + // class. Otherwise. the Cleaner would never be triggered. + private static class CleanerRunnable implements Runnable { + @NonNull + private final SelectionEventHelper mEventHelper; + @NonNull + private final TextClassifier mDelegate; + + CleanerRunnable( + @NonNull SelectionEventHelper eventHelper, @NonNull TextClassifier delegate) { + mEventHelper = Objects.requireNonNull(eventHelper); + mDelegate = Objects.requireNonNull(delegate); + } + + @Override + public void run() { + mEventHelper.endSession(); + mDelegate.destroy(); + } + } } diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.java b/core/java/android/view/textclassifier/TextClassificationSessionId.java index f90e6b2e407c..0b6fba225280 100644 --- a/core/java/android/view/textclassifier/TextClassificationSessionId.java +++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java @@ -17,6 +17,8 @@ package android.view.textclassifier; import android.annotation.NonNull; +import android.os.Binder; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -28,7 +30,10 @@ import java.util.UUID; * This class represents the id of a text classification session. */ public final class TextClassificationSessionId implements Parcelable { - private final @NonNull String mValue; + @NonNull + private final String mValue; + @NonNull + private final IBinder mToken; /** * Creates a new instance. @@ -36,7 +41,7 @@ public final class TextClassificationSessionId implements Parcelable { * @hide */ public TextClassificationSessionId() { - this(UUID.randomUUID().toString()); + this(UUID.randomUUID().toString(), new Binder()); } /** @@ -46,34 +51,28 @@ public final class TextClassificationSessionId implements Parcelable { * * @hide */ - public TextClassificationSessionId(@NonNull String value) { - mValue = value; + public TextClassificationSessionId(@NonNull String value, @NonNull IBinder token) { + mValue = Objects.requireNonNull(value); + mToken = Objects.requireNonNull(token); + } + + /** @hide */ + @NonNull + public IBinder getToken() { + return mToken; } @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + mValue.hashCode(); - return result; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TextClassificationSessionId that = (TextClassificationSessionId) o; + return Objects.equals(mValue, that.mValue) && Objects.equals(mToken, that.mToken); } @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - TextClassificationSessionId other = (TextClassificationSessionId) obj; - if (!mValue.equals(other.mValue)) { - return false; - } - return true; + public int hashCode() { + return Objects.hash(mValue, mToken); } @Override @@ -84,6 +83,7 @@ public final class TextClassificationSessionId implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mValue); + parcel.writeStrongBinder(mToken); } @Override @@ -96,28 +96,18 @@ public final class TextClassificationSessionId implements Parcelable { * * @return The flattened id. */ - public @NonNull String flattenToString() { + @NonNull + public String flattenToString() { return mValue; } - /** - * Unflattens a print job id from a string. - * - * @param string The string. - * @return The unflattened id, or null if the string is malformed. - * - * @hide - */ - public static @NonNull TextClassificationSessionId unflattenFromString(@NonNull String string) { - return new TextClassificationSessionId(string); - } - - public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationSessionId> CREATOR = + @NonNull + public static final Parcelable.Creator<TextClassificationSessionId> CREATOR = new Parcelable.Creator<TextClassificationSessionId>() { @Override public TextClassificationSessionId createFromParcel(Parcel parcel) { return new TextClassificationSessionId( - Objects.requireNonNull(parcel.readString())); + parcel.readString(), parcel.readStrongBinder()); } @Override diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 3322834c9ed1..086b9d83fed5 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -377,6 +377,11 @@ public final class SystemUiDeviceConfigFlags { public static final String NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN = "nav_bar_handle_show_over_lockscreen"; + /** + * (boolean) Whether to enable user-drag resizing for PIP. + */ + public static final String PIP_USER_RESIZE = "pip_user_resize"; + private SystemUiDeviceConfigFlags() { } } diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 571a33879411..c49c3a0f6b61 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -40,6 +40,7 @@ extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env); extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env); extern int register_android_graphics_Graphics(JNIEnv* env); extern int register_android_graphics_ImageDecoder(JNIEnv*); +extern int register_android_graphics_Interpolator(JNIEnv* env); extern int register_android_graphics_MaskFilter(JNIEnv* env); extern int register_android_graphics_NinePatch(JNIEnv*); extern int register_android_graphics_PathEffect(JNIEnv* env); @@ -115,6 +116,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)}, {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)}, {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)}, + {"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)}, {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)}, {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)}, {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)}, diff --git a/core/jni/android_os_incremental_IncrementalManager.cpp b/core/jni/android_os_incremental_IncrementalManager.cpp index d41e98241b07..44bff0188544 100644 --- a/core/jni/android_os_incremental_IncrementalManager.cpp +++ b/core/jni/android_os_incremental_IncrementalManager.cpp @@ -37,10 +37,28 @@ static jboolean nativeIsIncrementalPath(JNIEnv* env, return (jboolean)IncFs_IsIncFsPath(path.c_str()); } -static const JNINativeMethod method_table[] = { - {"nativeIsEnabled", "()Z", (void*)nativeIsEnabled}, - {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", (void*)nativeIsIncrementalPath}, -}; +static jbyteArray nativeUnsafeGetFileSignature(JNIEnv* env, jobject clazz, jstring javaPath) { + ScopedUtfChars path(env, javaPath); + + char signature[INCFS_MAX_SIGNATURE_SIZE]; + size_t size = sizeof(signature); + if (IncFs_UnsafeGetSignatureByPath(path.c_str(), signature, &size) < 0) { + return nullptr; + } + + jbyteArray result = env->NewByteArray(size); + if (result != nullptr) { + env->SetByteArrayRegion(result, 0, size, (const jbyte*)signature); + } + return result; +} + +static const JNINativeMethod method_table[] = {{"nativeIsEnabled", "()Z", (void*)nativeIsEnabled}, + {"nativeIsIncrementalPath", "(Ljava/lang/String;)Z", + (void*)nativeIsIncrementalPath}, + {"nativeUnsafeGetFileSignature", + "(Ljava/lang/String;)[B", + (void*)nativeUnsafeGetFileSignature}}; int register_android_os_incremental_IncrementalManager(JNIEnv* env) { return jniRegisterNativeMethods(env, "android/os/incremental/IncrementalManager", diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 92941b8cb22b..7290e3031d21 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -611,6 +611,12 @@ static void PreApplicationInit() { // Set the jemalloc decay time to 1. mallopt(M_DECAY_TIME, 1); + + // Maybe initialize GWP-ASan here. Must be called after + // mallopt(M_SET_ZYGOTE_CHILD). + bool ForceEnableGwpAsan = false; + android_mallopt(M_INITIALIZE_GWP_ASAN, &ForceEnableGwpAsan, + sizeof(ForceEnableGwpAsan)); } static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) { diff --git a/core/proto/android/content/locusid.proto b/core/proto/android/content/locusid.proto new file mode 100644 index 000000000000..4f0ce6b19e70 --- /dev/null +++ b/core/proto/android/content/locusid.proto @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package android.content; + +option java_multiple_files = true; + +// On disk representation of android.content.LocusId. Currently used by +// com.android.server.people.ConversationInfoProto. +message LocusIdProto { + optional string locus_id = 1; +} diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto new file mode 100644 index 000000000000..294b6efd3cbe --- /dev/null +++ b/core/proto/android/server/peopleservice.proto @@ -0,0 +1,75 @@ +/* + * 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.people; + +option java_multiple_files = true; + +import "frameworks/base/core/proto/android/content/locusid.proto"; + +// On disk data of conversation infos for a user and app package. +message ConversationInfosProto { + + // The series of conversation infos for a user and app package. + repeated ConversationInfoProto conversation_infos = 1; +} + +// Individual conversation info (com.android.server.people.data.ConversationInfo) for a user +// and app package. +message ConversationInfoProto { + + // The conversation's shortcut id. + optional string shortcut_id = 1; + + // The conversation's locus id. + optional .android.content.LocusIdProto locus_id_proto = 2; + + // The URI of the contact in the conversation. + optional string contact_uri = 3; + + // The notification channel id of the conversation. + optional string notification_channel_id = 4; + + // Integer representation of shortcut bit flags. + optional int32 shortcut_flags = 5; + + // Integer representation of conversation bit flags. + optional int32 conversation_flags = 6; +} + +// Individual event (com.android.server.people.data.Event). +message PeopleEventProto { + + // For valid values, refer to java class documentation. + optional int32 event_type = 1; + + optional int64 time = 2; + + // The duration of the event. Should only be set for some event_types. Refer to java class + // documentation for details. + optional int32 duration = 3; +} + +// Index of events' time distributions (com.android.server.people.data.EventIndex). +message PeopleEventIndexProto { + // Each long value in event_bitmaps represents a time slot, there should be 4 values. Further + // details can be found in class documentation. + repeated int64 event_bitmaps = 1; + + optional int64 last_updated_time = 2; +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ff696715e94d..71a42e454f30 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -96,6 +96,7 @@ <protected-broadcast android:name="android.intent.action.OVERLAY_PRIORITY_CHANGED" /> <protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" /> <protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" /> + <protected-broadcast android:name="android.intent.action.LOAD_DATA" /> <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" /> <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" /> @@ -2376,6 +2377,7 @@ @hide --> <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" android:protectionLevel="signature|installer|telephony" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <!-- Allows interaction across profiles in the same profile group. --> <permission android:name="android.permission.INTERACT_ACROSS_PROFILES" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 20901e04e6c5..7d8b8db9d4a0 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3790,6 +3790,12 @@ <attr name="description" /> <!-- Brief summary of the target of accessibility shortcut purpose or behavior. --> <attr name="summary" /> + <!-- Animated image of the target of accessibility shortcut purpose or behavior, to help + users understand how the target of accessibility shortcut can help them.--> + <attr name="animatedImageDrawable" format="reference"/> + <!-- Html description of the target of accessibility shortcut purpose or behavior, to help + users understand how the target of accessibility shortcut can help them. --> + <attr name="htmlDescription" format="string"/> </declare-styleable> <!-- Use <code>print-service</code> as the root tag of the XML resource that diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f17cd45cd880..df6330654e77 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2351,7 +2351,7 @@ type. These are flags and can be freely combined. 0 - disable whitelist (install all system packages; no logging) 1 - enforce (only install system packages if they are whitelisted) - 2 - log (log when a non-whitelisted package is run) + 2 - log (log non-whitelisted packages) 4 - any package not mentioned in the whitelist file is implicitly whitelisted on all users 8 - same as 4, but just for the SYSTEM user 16 - ignore OTAs (don't install system packages during OTAs) @@ -3792,6 +3792,10 @@ or empty if the default should be used. --> <string translatable="false" name="config_deviceSpecificAudioService"></string> + <!-- Class name of the device specific implementation of DisplayAreaPolicy.Provider + or empty if the default should be used. --> + <string translatable="false" name="config_deviceSpecificDisplayAreaPolicyProvider"></string> + <!-- Component name of media projection permission dialog --> <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a54566cfed17..08c840380fe5 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -414,6 +414,14 @@ logging. [CHAR LIMIT=NONE]--> <string name="network_logging_notification_text">Your organization manages this device and may monitor network traffic. Tap for details.</string> + <!-- Content title for a notification. This notification indicates that the device owner has + changed the location settings. [CHAR LIMIT=NONE] --> + <string name="location_changed_notification_title">Location settings changed by your admin</string> + <!-- Content text for a notification. Tapping opens device location settings. + [CHAR LIMIT=NONE] --> + <string name="location_changed_notification_text">Tap to see your location settings.</string> + + <!-- Factory reset warning dialog strings--> <skip /> <!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] --> <string name="factory_reset_warning">Your device will be erased</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2dd62c176e98..0babe48e832e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1213,6 +1213,8 @@ <java-symbol type="string" name="device_ownership_relinquished" /> <java-symbol type="string" name="network_logging_notification_title" /> <java-symbol type="string" name="network_logging_notification_text" /> + <java-symbol type="string" name="location_changed_notification_title" /> + <java-symbol type="string" name="location_changed_notification_text" /> <java-symbol type="string" name="personal_apps_suspended_notification_title" /> <java-symbol type="string" name="personal_apps_suspended_notification_text" /> <java-symbol type="string" name="factory_reset_warning" /> @@ -3864,6 +3866,8 @@ <!-- Toast message for background started foreground service while-in-use permission restriction feature --> <java-symbol type="string" name="allow_while_in_use_permission_in_fgs" /> + <java-symbol type="string" name="config_deviceSpecificDisplayAreaPolicyProvider" /> + <!-- Whether to expand the lock screen user switcher by default --> <java-symbol type="bool" name="config_expandLockScreenUserSwitcher" /> </resources> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 718ca46a4f18..59335a595334 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1330,12 +1330,6 @@ android:process=":FakeProvider"> </provider> - <provider - android:name="android.content.SlowProvider" - android:authorities="android.content.SlowProvider" - android:process=":SlowProvider"> - </provider> - <!-- Application components used for os tests --> <service android:name="android.os.MessengerService" diff --git a/core/tests/coretests/res/values/strings.xml b/core/tests/coretests/res/values/strings.xml index f630188e54dc..21613a82deef 100644 --- a/core/tests/coretests/res/values/strings.xml +++ b/core/tests/coretests/res/values/strings.xml @@ -148,4 +148,7 @@ <!-- Summary of the accessibility shortcut [CHAR LIMIT=NONE] --> <string name="accessibility_shortcut_summary">Accessibility shortcut summary</string> + + <!-- Html description of the accessibility shortcut [CHAR LIMIT=NONE] --> + <string name="accessibility_shortcut_html_description">Accessibility shortcut html description</string> </resources> diff --git a/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml b/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml index 60e29989ef0d..a597b7190fd7 100644 --- a/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml +++ b/core/tests/coretests/res/xml/accessibility_shortcut_test_activity.xml @@ -19,4 +19,6 @@ <accessibility-shortcut-target xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_shortcut_description" android:summary="@string/accessibility_shortcut_summary" + android:animatedImageDrawable="@drawable/bitmap_drawable" + android:htmlDescription="@string/accessibility_shortcut_html_description" />
\ No newline at end of file diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java index ae6d8df26feb..82a7b2c9217e 100644 --- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java @@ -84,6 +84,22 @@ public class AccessibilityShortcutInfoTest { } @Test + public void testAnimatedImageRes() { + assertThat("Animated image resource id is not correct", + mShortcutInfo.getAnimatedImageRes(), is(R.drawable.bitmap_drawable)); + } + + @Test + public void testHtmlDescription() { + final String htmlDescription = mTargetContext.getResources() + .getString(R.string.accessibility_shortcut_html_description); + + assertNotNull("Can't find html description string", htmlDescription); + assertThat("Html description is not correct", + mShortcutInfo.loadHtmlDescription(mPackageManager), is(htmlDescription)); + } + + @Test public void testEquals() { assertTrue(mShortcutInfo.equals(mShortcutInfo)); assertFalse(mShortcutInfo.equals(null)); diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java index 6dc7392945d8..9dcce1e51e0b 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -209,13 +209,4 @@ public class ContentResolverTest { String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderRemote")); assertEquals("fake/remote", type); } - - - @Test - public void testGetType_slowProvider() { - // This provider is running in a different process and is intentionally slow to start. - // We are trying to confirm that it does not cause an ANR - String type = mResolver.getType(Uri.parse("content://android.content.SlowProvider")); - assertEquals("slow", type); - } } diff --git a/core/tests/coretests/src/android/content/SlowProvider.java b/core/tests/coretests/src/android/content/SlowProvider.java deleted file mode 100644 index aba32e836e80..000000000000 --- a/core/tests/coretests/src/android/content/SlowProvider.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content; - -import android.database.Cursor; -import android.net.Uri; - -/** - * A dummy content provider for tests. This provider runs in a different process from the test and - * is intentionally slow. - */ -public class SlowProvider extends ContentProvider { - - private static final int ON_CREATE_LATENCY_MILLIS = 3000; - - @Override - public boolean onCreate() { - try { - Thread.sleep(ON_CREATE_LATENCY_MILLIS); - } catch (InterruptedException e) { - // Ignore - } - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - return null; - } - - @Override - public String getType(Uri uri) { - return "slow"; - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - return null; - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - return 0; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - return 0; - } -} diff --git a/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java new file mode 100644 index 000000000000..c897ace0e0b5 --- /dev/null +++ b/core/tests/coretests/src/android/content/integrity/InstallerAllowedByManifestFormulaTest.java @@ -0,0 +1,105 @@ +/* + * 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.content.integrity; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; + +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.Collections; + +public class InstallerAllowedByManifestFormulaTest { + + private static final InstallerAllowedByManifestFormula + FORMULA = new InstallerAllowedByManifestFormula(); + + @Test + public void testFormulaMatches_installerAndCertBothInManifest() { + AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder() + .setInstallerName("installer1") + .setInstallerCertificates(Arrays.asList("installer_cert1", "random_cert")) + .setAllowedInstallersAndCert(ImmutableMap.of( + "installer1", "installer_cert1", + "installer2", "installer_cert2" + )).build(); + + assertThat(FORMULA.matches(appInstallMetadata)).isTrue(); + } + + @Test + public void testFormulaMatches_installerAndCertDoesNotMatchInManifest() { + AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder() + .setInstallerName("installer1") + .setInstallerCertificates(Arrays.asList("installer_cert1", "random_cert")) + .setAllowedInstallersAndCert(ImmutableMap.of( + "installer1", "installer_cert2", + "installer2", "installer_cert1" + )).build(); + + assertThat(FORMULA.matches(appInstallMetadata)).isFalse(); + } + + @Test + public void testFormulaMatches_installerNotInManifest() { + AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder() + .setInstallerName("installer3") + .setInstallerCertificates(Arrays.asList("installer_cert1", "random_cert")) + .setAllowedInstallersAndCert(ImmutableMap.of( + "installer1", "installer_cert2", + "installer2", "installer_cert1" + )).build(); + + assertThat(FORMULA.matches(appInstallMetadata)).isFalse(); + } + + @Test + public void testFormulaMatches_certificateNotInManifest() { + AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder() + .setInstallerName("installer1") + .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert")) + .setAllowedInstallersAndCert(ImmutableMap.of( + "installer1", "installer_cert2", + "installer2", "installer_cert1" + )).build(); + + assertThat(FORMULA.matches(appInstallMetadata)).isFalse(); + } + + @Test + public void testFormulaMatches_emptyManifest() { + AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder() + .setInstallerName("installer1") + .setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert")) + .setAllowedInstallersAndCert(ImmutableMap.of()).build(); + + assertThat(FORMULA.matches(appInstallMetadata)).isTrue(); + } + + /** Returns a builder with all fields filled with some dummy data. */ + private AppInstallMetadata.Builder getAppInstallMetadataBuilder() { + return new AppInstallMetadata.Builder() + .setPackageName("abc") + .setAppCertificates(Collections.emptyList()) + .setInstallerCertificates(Collections.emptyList()) + .setInstallerName("abc") + .setVersionCode(-1) + .setIsPreInstalled(true); + } +} diff --git a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java index 75ef1f22b819..62c9c98f4e1d 100644 --- a/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java +++ b/core/tests/coretests/src/android/content/integrity/IntegrityFormulaTest.java @@ -20,8 +20,6 @@ import static android.content.integrity.IntegrityFormula.COMPOUND_FORMULA_TAG; import static com.google.common.truth.Truth.assertThat; -import static org.testng.Assert.assertThrows; - import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -32,8 +30,7 @@ public class IntegrityFormulaTest { @Test public void createEqualsFormula_packageName() { String packageName = "com.test.app"; - IntegrityFormula formula = - IntegrityFormula.PACKAGE_NAME.equalTo(packageName); + IntegrityFormula formula = IntegrityFormula.Application.packageNameEquals(packageName); AtomicFormula.StringAtomicFormula stringAtomicFormula = (AtomicFormula.StringAtomicFormula) formula; @@ -46,8 +43,7 @@ public class IntegrityFormulaTest { @Test public void createEqualsFormula_appCertificate() { String appCertificate = "com.test.app"; - IntegrityFormula formula = - IntegrityFormula.APP_CERTIFICATE.equalTo(appCertificate); + IntegrityFormula formula = IntegrityFormula.Application.certificatesContain(appCertificate); AtomicFormula.StringAtomicFormula stringAtomicFormula = (AtomicFormula.StringAtomicFormula) formula; @@ -60,8 +56,7 @@ public class IntegrityFormulaTest { @Test public void createEqualsFormula_installerName() { String installerName = "com.test.app"; - IntegrityFormula formula = - IntegrityFormula.INSTALLER_NAME.equalTo(installerName); + IntegrityFormula formula = IntegrityFormula.Installer.packageNameEquals(installerName); AtomicFormula.StringAtomicFormula stringAtomicFormula = (AtomicFormula.StringAtomicFormula) formula; @@ -75,7 +70,7 @@ public class IntegrityFormulaTest { public void createEqualsFormula_installerCertificate() { String installerCertificate = "com.test.app"; IntegrityFormula formula = - IntegrityFormula.INSTALLER_CERTIFICATE.equalTo(installerCertificate); + IntegrityFormula.Installer.certificatesContain(installerCertificate); AtomicFormula.StringAtomicFormula stringAtomicFormula = (AtomicFormula.StringAtomicFormula) formula; @@ -88,8 +83,7 @@ public class IntegrityFormulaTest { @Test public void createEqualsFormula_versionCode() { int versionCode = 12; - IntegrityFormula formula = - IntegrityFormula.VERSION_CODE.equalTo(versionCode); + IntegrityFormula formula = IntegrityFormula.Application.versionCodeEquals(versionCode); AtomicFormula.LongAtomicFormula stringAtomicFormula = (AtomicFormula.LongAtomicFormula) formula; @@ -100,24 +94,9 @@ public class IntegrityFormulaTest { } @Test - public void createEqualsFormula_invalidKeyTypeForStringParameter() { - assertThrows( - IllegalArgumentException.class, - () -> IntegrityFormula.PRE_INSTALLED.equalTo("wrongString")); - } - - @Test - public void createEqualsFormula_invalidKeyTypeForLongParameter() { - assertThrows( - IllegalArgumentException.class, - () -> IntegrityFormula.PACKAGE_NAME.equalTo(12)); - } - - @Test public void createGreaterThanFormula_versionCode() { int versionCode = 12; - IntegrityFormula formula = - IntegrityFormula.VERSION_CODE.greaterThan(versionCode); + IntegrityFormula formula = IntegrityFormula.Application.versionCodeGreaterThan(versionCode); AtomicFormula.LongAtomicFormula stringAtomicFormula = (AtomicFormula.LongAtomicFormula) formula; @@ -128,17 +107,10 @@ public class IntegrityFormulaTest { } @Test - public void createGreaterThanFormula_invalidKeyTypeForLongParameter() { - assertThrows( - IllegalArgumentException.class, - () -> IntegrityFormula.PACKAGE_NAME.greaterThan(12)); - } - - @Test public void createGreaterThanOrEqualsToFormula_versionCode() { int versionCode = 12; - IntegrityFormula formula = - IntegrityFormula.VERSION_CODE.greaterThanOrEquals(versionCode); + IntegrityFormula formula = IntegrityFormula.Application.versionCodeGreaterThanOrEqualTo( + versionCode); AtomicFormula.LongAtomicFormula stringAtomicFormula = (AtomicFormula.LongAtomicFormula) formula; @@ -149,15 +121,8 @@ public class IntegrityFormulaTest { } @Test - public void createGreaterThanOrEqualsToFormula_invalidKeyTypeForLongParameter() { - assertThrows( - IllegalArgumentException.class, - () -> IntegrityFormula.PACKAGE_NAME.greaterThanOrEquals(12)); - } - - @Test public void createIsTrueFormula_preInstalled() { - IntegrityFormula formula = IntegrityFormula.PRE_INSTALLED.equalTo(true); + IntegrityFormula formula = IntegrityFormula.Application.isPreInstalled(); AtomicFormula.BooleanAtomicFormula stringAtomicFormula = (AtomicFormula.BooleanAtomicFormula) formula; @@ -167,20 +132,12 @@ public class IntegrityFormulaTest { } @Test - public void createIsTrueFormula_invalidKeyTypeForBoolParameter() { - assertThrows( - IllegalArgumentException.class, - () -> IntegrityFormula.PACKAGE_NAME.equalTo(true)); - } - - @Test public void createAllFormula() { String packageName = "com.test.package"; String certificateName = "certificate"; - IntegrityFormula formula1 = - IntegrityFormula.PACKAGE_NAME.equalTo(packageName); - IntegrityFormula formula2 = - IntegrityFormula.APP_CERTIFICATE.equalTo(certificateName); + IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName); + IntegrityFormula formula2 = IntegrityFormula.Application.certificatesContain( + certificateName); IntegrityFormula compoundFormula = IntegrityFormula.all(formula1, formula2); @@ -191,10 +148,9 @@ public class IntegrityFormulaTest { public void createAnyFormula() { String packageName = "com.test.package"; String certificateName = "certificate"; - IntegrityFormula formula1 = - IntegrityFormula.PACKAGE_NAME.equalTo(packageName); - IntegrityFormula formula2 = - IntegrityFormula.APP_CERTIFICATE.equalTo(certificateName); + IntegrityFormula formula1 = IntegrityFormula.Application.packageNameEquals(packageName); + IntegrityFormula formula2 = IntegrityFormula.Application.certificatesContain( + certificateName); IntegrityFormula compoundFormula = IntegrityFormula.any(formula1, formula2); @@ -206,8 +162,7 @@ public class IntegrityFormulaTest { String packageName = "com.test.package"; IntegrityFormula compoundFormula = - IntegrityFormula.not( - IntegrityFormula.PACKAGE_NAME.equalTo(packageName)); + IntegrityFormula.not(IntegrityFormula.Application.packageNameEquals(packageName)); assertThat(compoundFormula.getTag()).isEqualTo(COMPOUND_FORMULA_TAG); } diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java index 2c956c990e97..669138c15698 100644 --- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java +++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java @@ -148,6 +148,15 @@ public class ConfigurationTest extends TestCase { assertEquals(SMALLEST_SCREEN_WIDTH_DP_UNDEFINED, config.smallestScreenWidthDp); } + @Test + public void testNightModeHelper() { + Configuration config = new Configuration(); + config.uiMode = Configuration.UI_MODE_NIGHT_YES; + assertTrue(config.isNightModeActive()); + config.uiMode = Configuration.UI_MODE_NIGHT_NO; + assertFalse(config.isNightModeActive()); + } + private void dumpDebug(File f, Configuration config) throws Exception { final AtomicFile af = new AtomicFile(f); FileOutputStream fos = af.startWrite(); diff --git a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java index 49fb75bf6a45..b8dbfd3186c5 100644 --- a/core/tests/coretests/src/android/database/DatabaseGeneralTest.java +++ b/core/tests/coretests/src/android/database/DatabaseGeneralTest.java @@ -466,179 +466,7 @@ public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceT // expected } } - - @MediumTest - public void testTokenize() throws Exception { - Cursor c; - mDatabase.execSQL("CREATE TABLE tokens (" + - "token TEXT COLLATE unicode," + - "source INTEGER," + - "token_index INTEGER," + - "tag TEXT" + - ");"); - mDatabase.execSQL("CREATE TABLE tokens_no_index (" + - "token TEXT COLLATE unicode," + - "source INTEGER" + - ");"); - - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE(NULL, NULL, NULL, NULL)", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', NULL, NULL, NULL)", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 10, NULL, NULL)", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 10, 'some string', NULL)", null)); - - Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 11, 'some string ok', ' ', 1, 'foo')", null)); - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 11, 'second field', ' ', 1, 'bar')", null)); - - Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens_no_index', 20, 'some string ok', ' ')", null)); - Assert.assertEquals(3, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens_no_index', 21, 'foo bar baz', ' ', 0)", null)); - - // test Chinese - String chinese = new String("\u4eac\u4ec5 \u5c3d\u5f84\u60ca"); - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 12,'" + chinese + "', ' ', 1)", null)); - - String icustr = new String("Fr\u00e9d\u00e9ric Hj\u00f8nnev\u00e5g"); - - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT _TOKENIZE('tokens', 13, '" + icustr + "', ' ', 1)", null)); - - Assert.assertEquals(9, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens;", null)); - - String key = DatabaseUtils.getHexCollationKey("Frederic Hjonneva"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("Hjonneva"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(13, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("some string ok"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("string"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("ok"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(2, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("foo", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("second field"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - key = DatabaseUtils.getHexCollationKey("field"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(11, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals("bar", DatabaseUtils.stringForQuery(mDatabase, - "SELECT tag from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey(chinese); - String[] a = new String[1]; - a[0] = key; - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token= ?", a)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token= ?", a)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token= ?", a)); - a[0] += "*"; - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB ?", a)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB ?", a)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB ?", a)); - - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token= '" + key + "'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token= '" + key + "'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token= '" + key + "'", null)); - - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("\u4eac\u4ec5"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("\u5c3d\u5f84\u60ca"); - Log.d("DatabaseGeneralTest", "key = " + key); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(12, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens where token GLOB '" + key + "*'", null)); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT token_index from tokens where token GLOB '" + key + "*'", null)); - - Assert.assertEquals(0, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens where token GLOB 'ab*'", null)); - - key = DatabaseUtils.getHexCollationKey("some string ok"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null)); - Assert.assertEquals(20, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null)); - - key = DatabaseUtils.getHexCollationKey("bar"); - Assert.assertEquals(1, DatabaseUtils.longForQuery(mDatabase, - "SELECT count(*) from tokens_no_index where token GLOB '" + key + "*'", null)); - Assert.assertEquals(21, DatabaseUtils.longForQuery(mDatabase, - "SELECT source from tokens_no_index where token GLOB '" + key + "*'", null)); - } - + @MediumTest public void testTransactions() throws Exception { mDatabase.execSQL("CREATE TABLE test (num INTEGER);"); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java index a2d23558616b..8dbb5f544ad4 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -206,6 +206,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { blockModes, userAuthenticationRequired, (int) userAuthenticationValidityDurationSeconds, + keymasterHwEnforcedUserAuthenticators, userAuthenticationRequirementEnforcedBySecureHardware, userAuthenticationValidWhileOnBody, trustedUserPresenceRequred, diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 450dd3301253..d683041fbfdc 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -263,6 +263,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; + private final @KeyProperties.AuthEnum int mUserAuthenticationType; private final boolean mUserPresenceRequired; private final byte[] mAttestationChallenge; private final boolean mUniqueIdIncluded; @@ -301,6 +302,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, + @KeyProperties.AuthEnum int userAuthenticationType, boolean userPresenceRequired, byte[] attestationChallenge, boolean uniqueIdIncluded, @@ -352,6 +354,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserAuthenticationRequired = userAuthenticationRequired; mUserPresenceRequired = userPresenceRequired; mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + mUserAuthenticationType = userAuthenticationType; mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge); mUniqueIdIncluded = uniqueIdIncluded; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; @@ -605,6 +608,22 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Gets the modes of authentication that can authorize use of this key. This has effect only if + * user authentication is required (see {@link #isUserAuthenticationRequired()}). + * + * <p>This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @return integer representing the bitwse OR of all acceptable authentication types for the + * key. + * + * @see #isUserAuthenticationRequired() + * @see Builder#setUserAuthenticationParameters(int, int) + */ + public @KeyProperties.AuthEnum int getUserAuthenticationType() { + return mUserAuthenticationType; + } + /** * Returns {@code true} if the key is authorized to be used only if a test of user presence has * been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls. * It requires that the KeyStore implementation have a direct way to validate the user presence @@ -746,6 +765,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; private int mUserAuthenticationValidityDurationSeconds = -1; + private @KeyProperties.AuthEnum int mUserAuthenticationType; private boolean mUserPresenceRequired = false; private byte[] mAttestationChallenge = null; private boolean mUniqueIdIncluded = false; @@ -810,6 +830,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserAuthenticationRequired = sourceSpec.isUserAuthenticationRequired(); mUserAuthenticationValidityDurationSeconds = sourceSpec.getUserAuthenticationValidityDurationSeconds(); + mUserAuthenticationType = sourceSpec.getUserAuthenticationType(); mUserPresenceRequired = sourceSpec.isUserPresenceRequired(); mAttestationChallenge = sourceSpec.getAttestationChallenge(); mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded(); @@ -1207,14 +1228,62 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @see BiometricPrompt * @see BiometricPrompt.CryptoObject * @see KeyguardManager + * @deprecated See {@link #setUserAuthenticationParameters(int, int)} */ + @Deprecated @NonNull public Builder setUserAuthenticationValidityDurationSeconds( @IntRange(from = -1) int seconds) { if (seconds < -1) { throw new IllegalArgumentException("seconds must be -1 or larger"); } - mUserAuthenticationValidityDurationSeconds = seconds; + if (seconds == -1) { + return setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG); + } + return setUserAuthenticationParameters(seconds, KeyProperties.AUTH_BIOMETRIC_STRONG); + } + + /** + * Sets the duration of time (seconds) and authorization type for which this key is + * authorized to be used after the user is successfully authenticated. This has effect if + * the key requires user authentication for its use (see + * {@link #setUserAuthenticationRequired(boolean)}). + * + * <p>By default, if user authentication is required, it must take place for every use of + * the key. + * + * <p>These cryptographic operations will throw {@link UserNotAuthenticatedException} during + * initialization if the user needs to be authenticated to proceed. This situation can be + * resolved by the user authenticating with the appropriate biometric or credential as + * required by the key. See {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)} + * and {@link BiometricManager.Authenticators}. + * + * <p>Once resolved, initializing a new cryptographic operation using this key (or any other + * key which is authorized to be used for a fixed duration of time after user + * authentication) should succeed provided the user authentication flow completed + * successfully. + * + * @param timeout duration in seconds or {@code 0} if user authentication must take place + * for every use of the key. {@code -1} is also accepted for legacy purposes. It is + * functionally the same as {@code 0}. + * @param type set of authentication types which can authorize use of the key. See + * {@link KeyProperties}.{@code AUTH} flags. + * + * @see #setUserAuthenticationRequired(boolean) + * @see BiometricPrompt + * @see BiometricPrompt.CryptoObject + * @see KeyguardManager + */ + @NonNull + public Builder setUserAuthenticationParameters(@IntRange(from = -1) int timeout, + @KeyProperties.AuthEnum int type) { + if (timeout < -1) { + throw new IllegalArgumentException("timeout must be -1 or larger"); + } else if (timeout == -1) { + timeout = 0; + } + mUserAuthenticationValidityDurationSeconds = timeout; + mUserAuthenticationType = type; return this; } @@ -1392,6 +1461,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mRandomizedEncryptionRequired, mUserAuthenticationRequired, mUserAuthenticationValidityDurationSeconds, + mUserAuthenticationType, mUserPresenceRequired, mAttestationChallenge, mUniqueIdIncluded, diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java index 0a75cd5268b7..d891a25dba68 100644 --- a/keystore/java/android/security/keystore/KeyInfo.java +++ b/keystore/java/android/security/keystore/KeyInfo.java @@ -78,6 +78,7 @@ public class KeyInfo implements KeySpec { private final @KeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; + private final @KeyProperties.AuthEnum int mUserAuthenticationType; private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware; private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mTrustedUserPresenceRequired; @@ -101,6 +102,7 @@ public class KeyInfo implements KeySpec { @KeyProperties.BlockModeEnum String[] blockModes, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, + @KeyProperties.AuthEnum int userAuthenticationType, boolean userAuthenticationRequirementEnforcedBySecureHardware, boolean userAuthenticationValidWhileOnBody, boolean trustedUserPresenceRequired, @@ -122,6 +124,7 @@ public class KeyInfo implements KeySpec { mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); mUserAuthenticationRequired = userAuthenticationRequired; mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; + mUserAuthenticationType = userAuthenticationType; mUserAuthenticationRequirementEnforcedBySecureHardware = userAuthenticationRequirementEnforcedBySecureHardware; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; @@ -301,6 +304,22 @@ public class KeyInfo implements KeySpec { } /** + * Gets the acceptable user authentication types for which this key can be authorized to be + * used. This has effect only if user authentication is required (see + * {@link #isUserAuthenticationRequired()}). + * + * <p>This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @return integer representing the accepted forms of user authentication for this key + * + * @see #isUserAuthenticationRequired() + */ + public @KeyProperties.AuthEnum int getUserAuthenticationType() { + return mUserAuthenticationType; + } + + /** * Returns {@code true} if the requirement that this key can only be used if the user has been * authenticated is enforced by secure hardware (e.g., Trusted Execution Environment (TEE) or * Secure Element (SE)). diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index f12a659039ee..c58a1236d475 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -39,6 +39,27 @@ public abstract class KeyProperties { * @hide */ @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "AUTH_" }, value = { + AUTH_BIOMETRIC_STRONG, + AUTH_DEVICE_CREDENTIAL, + }) + public @interface AuthEnum {} + + /** + * The non-biometric credential used to secure the device (i.e., PIN, pattern, or password) + */ + public static final int AUTH_DEVICE_CREDENTIAL = 1 << 0; + + /** + * Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the + * requirements for <strong>Strong</strong>, as defined by the Android CDD. + */ + public static final int AUTH_BIOMETRIC_STRONG = 1 << 1; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "PURPOSE_" }, value = { PURPOSE_ENCRYPT, PURPOSE_DECRYPT, diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 26181a65dc1d..e230b7c3708b 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -225,6 +225,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private final @KeyProperties.BlockModeEnum String[] mBlockModes; private final boolean mRandomizedEncryptionRequired; private final boolean mUserAuthenticationRequired; + private final @KeyProperties.AuthEnum int mUserAuthenticationType; private final int mUserAuthenticationValidityDurationSeconds; private final boolean mUserPresenceRequred; private final boolean mUserAuthenticationValidWhileOnBody; @@ -246,6 +247,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { @KeyProperties.BlockModeEnum String[] blockModes, boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, + @KeyProperties.AuthEnum int userAuthenticationType, int userAuthenticationValidityDurationSeconds, boolean userPresenceRequred, boolean userAuthenticationValidWhileOnBody, @@ -267,6 +269,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes)); mRandomizedEncryptionRequired = randomizedEncryptionRequired; mUserAuthenticationRequired = userAuthenticationRequired; + mUserAuthenticationType = userAuthenticationType; mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; mUserPresenceRequred = userPresenceRequred; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; @@ -429,6 +432,10 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { return mUserConfirmationRequired; } + public @KeyProperties.AuthEnum int getUserAuthenticationType() { + return mUserAuthenticationType; + } + /** * Gets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect only if user authentication is required @@ -555,6 +562,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private @KeyProperties.BlockModeEnum String[] mBlockModes; private boolean mRandomizedEncryptionRequired = true; private boolean mUserAuthenticationRequired; + private @KeyProperties.AuthEnum int mUserAuthenticationType; private int mUserAuthenticationValidityDurationSeconds = -1; private boolean mUserPresenceRequired = false; private boolean mUserAuthenticationValidWhileOnBody; @@ -850,14 +858,62 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see BiometricPrompt * @see BiometricPrompt.CryptoObject * @see KeyguardManager + * @deprecated See {@link #setUserAuthenticationParameters(int, int)} */ + @Deprecated @NonNull public Builder setUserAuthenticationValidityDurationSeconds( @IntRange(from = -1) int seconds) { if (seconds < -1) { throw new IllegalArgumentException("seconds must be -1 or larger"); } - mUserAuthenticationValidityDurationSeconds = seconds; + if (seconds == -1) { + return setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG); + } + return setUserAuthenticationParameters(seconds, KeyProperties.AUTH_BIOMETRIC_STRONG); + } + + /** + * Sets the duration of time (seconds) and authorization type for which this key is + * authorized to be used after the user is successfully authenticated. This has effect if + * the key requires user authentication for its use (see + * {@link #setUserAuthenticationRequired(boolean)}). + * + * <p>By default, if user authentication is required, it must take place for every use of + * the key. + * + * <p>These cryptographic operations will throw {@link UserNotAuthenticatedException} during + * initialization if the user needs to be authenticated to proceed. This situation can be + * resolved by the user authenticating with the appropriate biometric or credential as + * required by the key. See {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)} + * and {@link BiometricManager.Authenticators}. + * + * <p>Once resolved, initializing a new cryptographic operation using this key (or any other + * key which is authorized to be used for a fixed duration of time after user + * authentication) should succeed provided the user authentication flow completed + * successfully. + * + * @param timeout duration in seconds or {@code 0} if user authentication must take place + * for every use of the key. {@code -1} is also accepted for legacy purposes. It is + * functionally the same as {@code 0}. + * @param type set of authentication types which can authorize use of the key. See + * {@link KeyProperties}.{@code AUTH} flags. + * + * @see #setUserAuthenticationRequired(boolean) + * @see BiometricPrompt + * @see BiometricPrompt.CryptoObject + * @see KeyguardManager + */ + @NonNull + public Builder setUserAuthenticationParameters(@IntRange(from = -1) int timeout, + @KeyProperties.AuthEnum int type) { + if (timeout < -1) { + throw new IllegalArgumentException("timeout must be -1 or larger"); + } else if (timeout == -1) { + timeout = 0; + } + mUserAuthenticationValidityDurationSeconds = timeout; + mUserAuthenticationType = type; return this; } @@ -1002,6 +1058,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mBlockModes, mRandomizedEncryptionRequired, mUserAuthenticationRequired, + mUserAuthenticationType, mUserAuthenticationValidityDurationSeconds, mUserPresenceRequired, mUserAuthenticationValidWhileOnBody, diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java index 79e48cdfdd8e..37b1f23ba55a 100644 --- a/keystore/java/android/security/keystore/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -88,17 +88,9 @@ public abstract class KeymasterUtils { * Adds keymaster arguments to express the key's authorization policy supported by user * authentication. * - * @param userAuthenticationRequired whether user authentication is required to authorize the - * use of the key. - * @param userAuthenticationValidityDurationSeconds duration of time (seconds) for which user - * authentication is valid as authorization for using the key or {@code -1} if every - * use of the key needs authorization. - * @param boundToSpecificSecureUserId if non-zero, specify which SID the key will be bound to, - * overriding the default logic in this method where the key is bound to either the root - * SID of the current user, or the fingerprint SID if explicit fingerprint authorization - * is requested. - * @param userConfirmationRequired whether user confirmation is required to authorize the use - * of the key. + * @param args The arguments sent to keymaster that need to be populated from the spec + * @param spec The user authentication relevant portions of the spec passed in from the caller. + * This spec will be translated into the relevant keymaster tags to be loaded into args. * @throws IllegalStateException if user authentication is required but the system is in a wrong * state (e.g., secure lock screen not set up) for generating or importing keys that * require user authentication. @@ -122,7 +114,7 @@ public abstract class KeymasterUtils { return; } - if (spec.getUserAuthenticationValidityDurationSeconds() == -1) { + if (spec.getUserAuthenticationValidityDurationSeconds() == 0) { PackageManager pm = KeyStore.getApplicationContext().getPackageManager(); // Every use of this key needs to be authorized by the user. This currently means // fingerprint or face auth. @@ -168,7 +160,8 @@ public abstract class KeymasterUtils { args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, KeymasterArguments.toUint64(sids.get(i))); } - args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_BIOMETRIC); + + args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType()); if (spec.isUserAuthenticationValidWhileOnBody()) { throw new ProviderException("Key validity extension while device is on-body is not " diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index 98e458930a7f..9c9773e5d145 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -97,6 +97,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { out.writeBoolean(mSpec.isRandomizedEncryptionRequired()); out.writeBoolean(mSpec.isUserAuthenticationRequired()); out.writeInt(mSpec.getUserAuthenticationValidityDurationSeconds()); + out.writeInt(mSpec.getUserAuthenticationType()); out.writeBoolean(mSpec.isUserPresenceRequired()); out.writeByteArray(mSpec.getAttestationChallenge()); out.writeBoolean(mSpec.isUniqueIdIncluded()); @@ -153,6 +154,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { final boolean randomizedEncryptionRequired = in.readBoolean(); final boolean userAuthenticationRequired = in.readBoolean(); final int userAuthenticationValidityDurationSeconds = in.readInt(); + final int userAuthenticationTypes = in.readInt(); final boolean userPresenceRequired = in.readBoolean(); final byte[] attestationChallenge = in.createByteArray(); final boolean uniqueIdIncluded = in.readBoolean(); @@ -185,6 +187,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { randomizedEncryptionRequired, userAuthenticationRequired, userAuthenticationValidityDurationSeconds, + userAuthenticationTypes, userPresenceRequired, attestationChallenge, uniqueIdIncluded, diff --git a/keystore/java/android/security/keystore/UserAuthArgs.java b/keystore/java/android/security/keystore/UserAuthArgs.java index 69520606f101..c9e9bf0ab82f 100644 --- a/keystore/java/android/security/keystore/UserAuthArgs.java +++ b/keystore/java/android/security/keystore/UserAuthArgs.java @@ -28,6 +28,7 @@ public interface UserAuthArgs { boolean isUserAuthenticationRequired(); int getUserAuthenticationValidityDurationSeconds(); + @KeyProperties.AuthEnum int getUserAuthenticationType(); boolean isUserAuthenticationValidWhileOnBody(); boolean isInvalidatedByBiometricEnrollment(); boolean isUserConfirmationRequired(); diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 8cfd2d8ca696..32086625a726 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -992,6 +992,11 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid) { return bag; } +static bool compare_bag_entries(const ResolvedBag::Entry& entry1, + const ResolvedBag::Entry& entry2) { + return entry1.key < entry2.key; +} + const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids) { auto cached_iter = cached_bags_.find(resid); if (cached_iter != cached_bags_.end()) { @@ -1027,13 +1032,15 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& child_resids.push_back(resid); uint32_t parent_resid = dtohl(map->parent.ident); - if (parent_resid == 0 || std::find(child_resids.begin(), child_resids.end(), parent_resid) + if (parent_resid == 0U || std::find(child_resids.begin(), child_resids.end(), parent_resid) != child_resids.end()) { - // There is no parent or that a circular dependency exist, meaning there is nothing to - // inherit and we can do a simple copy of the entries in the map. + // There is no parent or a circular dependency exist, meaning there is nothing to inherit and + // we can do a simple copy of the entries in the map. const size_t entry_count = map_entry_end - map_entry; util::unique_cptr<ResolvedBag> new_bag{reinterpret_cast<ResolvedBag*>( malloc(sizeof(ResolvedBag) + (entry_count * sizeof(ResolvedBag::Entry))))}; + + bool sort_entries = false; ResolvedBag::Entry* new_entry = new_bag->entries; for (; map_entry != map_entry_end; ++map_entry) { uint32_t new_key = dtohl(map_entry->name.ident); @@ -1059,8 +1066,15 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& new_entry->value.data, new_key); return nullptr; } + sort_entries = sort_entries || + (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key)); ++new_entry; } + + if (sort_entries) { + std::sort(new_bag->entries, new_bag->entries + entry_count, compare_bag_entries); + } + new_bag->type_spec_flags = entry.type_flags; new_bag->entry_count = static_cast<uint32_t>(entry_count); ResolvedBag* result = new_bag.get(); @@ -1091,6 +1105,7 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& const ResolvedBag::Entry* const parent_entry_end = parent_entry + parent_bag->entry_count; // The keys are expected to be in sorted order. Merge the two bags. + bool sort_entries = false; while (map_entry != map_entry_end && parent_entry != parent_entry_end) { uint32_t child_key = dtohl(map_entry->name.ident); if (!is_internal_resid(child_key)) { @@ -1123,6 +1138,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& memcpy(new_entry, parent_entry, sizeof(*new_entry)); } + sort_entries = sort_entries || + (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key)); if (child_key >= parent_entry->key) { // Move to the next parent entry if we used it or it was overridden. ++parent_entry; @@ -1153,6 +1170,8 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& new_entry->value.dataType, new_entry->value.data, new_key); return nullptr; } + sort_entries = sort_entries || + (new_entry != new_bag->entries && (new_entry->key < (new_entry - 1U)->key)); ++map_entry; ++new_entry; } @@ -1172,6 +1191,10 @@ const ResolvedBag* AssetManager2::GetBag(uint32_t resid, std::vector<uint32_t>& new_bag.release(), sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry))))); } + if (sort_entries) { + std::sort(new_bag->entries, new_bag->entries + actual_count, compare_bag_entries); + } + // Combine flags from the parent and our own bag. new_bag->type_spec_flags = entry.type_flags | parent_bag->type_spec_flags; new_bag->entry_count = static_cast<uint32_t>(actual_count); diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 2f6f3dfcaf1c..35fea7ab86cb 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -285,6 +285,27 @@ TEST_F(AssetManager2Test, FindsBagResourceFromSharedLibrary) { EXPECT_EQ(0x03, get_package_id(bag->entries[1].key)); } +TEST_F(AssetManager2Test, FindsBagResourceFromMultipleSharedLibraries) { + AssetManager2 assetmanager; + + // libclient is built with lib_one and then lib_two in order. + // Reverse the order to test that proper package ID re-assignment is happening. + assetmanager.SetApkAssets( + {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()}); + + const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::ThemeMultiLib); + ASSERT_NE(nullptr, bag); + ASSERT_EQ(bag->entry_count, 2u); + + // First attribute comes from lib_two. + EXPECT_EQ(2, bag->entries[0].cookie); + EXPECT_EQ(0x02, get_package_id(bag->entries[0].key)); + + // The next two attributes come from lib_one. + EXPECT_EQ(2, bag->entries[1].cookie); + EXPECT_EQ(0x03, get_package_id(bag->entries[1].key)); +} + TEST_F(AssetManager2Test, FindsStyleResourceWithParentFromSharedLibrary) { AssetManager2 assetmanager; diff --git a/libs/androidfw/tests/data/lib_two/R.h b/libs/androidfw/tests/data/lib_two/R.h index 92b9cc10e7a8..fd5a910961cb 100644 --- a/libs/androidfw/tests/data/lib_two/R.h +++ b/libs/androidfw/tests/data/lib_two/R.h @@ -30,16 +30,22 @@ struct R { }; }; + struct integer { + enum : uint32_t { + bar = 0x02020000, // default + }; + }; + struct string { enum : uint32_t { - LibraryString = 0x02020000, // default - foo = 0x02020001, // default + LibraryString = 0x02030000, // default + foo = 0x02030001, // default }; }; struct style { enum : uint32_t { - Theme = 0x02030000, // default + Theme = 0x02040000, // default }; }; }; diff --git a/libs/androidfw/tests/data/lib_two/lib_two.apk b/libs/androidfw/tests/data/lib_two/lib_two.apk Binary files differindex 486c23000276..8193db637eed 100644 --- a/libs/androidfw/tests/data/lib_two/lib_two.apk +++ b/libs/androidfw/tests/data/lib_two/lib_two.apk diff --git a/libs/androidfw/tests/data/lib_two/res/values/values.xml b/libs/androidfw/tests/data/lib_two/res/values/values.xml index 340d14c34c5d..4e1d69aa5a5a 100644 --- a/libs/androidfw/tests/data/lib_two/res/values/values.xml +++ b/libs/androidfw/tests/data/lib_two/res/values/values.xml @@ -18,14 +18,17 @@ <public type="attr" name="attr3" id="0x00010000" /> <attr name="attr3" format="integer" /> - <public type="string" name="LibraryString" id="0x00020000" /> + <public type="integer" name="bar" id="0x00020000" /> + <integer name="bar">1337</integer> + + <public type="string" name="LibraryString" id="0x00030000" /> <string name="LibraryString">Hi from library two</string> - <public type="string" name="foo" id="0x00020001" /> + <public type="string" name="foo" id="0x00030001" /> <string name="foo">Foo from lib_two</string> - <public type="style" name="Theme" id="0x02030000" /> + <public type="style" name="Theme" id="0x00040000" /> <style name="Theme"> - <item name="com.android.lib_two:attr3">800</item> + <item name="com.android.lib_two:attr3">@integer/bar</item> </style> </resources> diff --git a/libs/androidfw/tests/data/libclient/R.h b/libs/androidfw/tests/data/libclient/R.h index 43d1f9bb68e7..e21b3ebae826 100644 --- a/libs/androidfw/tests/data/libclient/R.h +++ b/libs/androidfw/tests/data/libclient/R.h @@ -34,6 +34,7 @@ struct R { struct style { enum : uint32_t { Theme = 0x7f020000, // default + ThemeMultiLib = 0x7f020001, // default }; }; diff --git a/libs/androidfw/tests/data/libclient/libclient.apk b/libs/androidfw/tests/data/libclient/libclient.apk Binary files differindex 17990248e862..4b9a8833c64a 100644 --- a/libs/androidfw/tests/data/libclient/libclient.apk +++ b/libs/androidfw/tests/data/libclient/libclient.apk diff --git a/libs/androidfw/tests/data/libclient/res/values/values.xml b/libs/androidfw/tests/data/libclient/res/values/values.xml index fead7c323767..a29f473e3c17 100644 --- a/libs/androidfw/tests/data/libclient/res/values/values.xml +++ b/libs/androidfw/tests/data/libclient/res/values/values.xml @@ -27,6 +27,12 @@ <item name="bar">@com.android.lib_one:string/foo</item> </style> + <public type="style" name="ThemeMultiLib" id="0x7f020001" /> + <style name="ThemeMultiLib" > + <item name="com.android.lib_one:attr1">@com.android.lib_one:string/foo</item> + <item name="com.android.lib_two:attr3">@com.android.lib_two:integer/bar</item> + </style> + <public type="string" name="foo_one" id="0x7f030000" /> <string name="foo_one">@com.android.lib_one:string/foo</string> diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java index 944ebf937dc8..f075a53829ce 100644 --- a/location/java/android/location/AbstractListenerManager.java +++ b/location/java/android/location/AbstractListenerManager.java @@ -27,6 +27,7 @@ import android.util.ArrayMap; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -35,26 +36,34 @@ import java.util.function.Consumer; * * @hide */ -abstract class AbstractListenerManager<T> { +abstract class AbstractListenerManager<TRequest, TListener> { - private static class Registration<T> { + private static class Registration<TRequest, TListener> { private final Executor mExecutor; - @Nullable private volatile T mListener; + @Nullable private TRequest mRequest; + @Nullable private volatile TListener mListener; - private Registration(Executor executor, T listener) { + private Registration(@Nullable TRequest request, Executor executor, TListener listener) { Preconditions.checkArgument(listener != null, "invalid null listener/callback"); Preconditions.checkArgument(executor != null, "invalid null executor"); mExecutor = executor; mListener = listener; + mRequest = request; + } + + @Nullable + public TRequest getRequest() { + return mRequest; } private void unregister() { + mRequest = null; mListener = null; } - private void execute(Consumer<T> operation) { + private void execute(Consumer<TListener> operation) { mExecutor.execute(() -> { - T listener = mListener; + TListener listener = mListener; if (listener == null) { return; } @@ -71,71 +80,135 @@ abstract class AbstractListenerManager<T> { } @GuardedBy("mListeners") - private final ArrayMap<Object, Registration<T>> mListeners = new ArrayMap<>(); + private final ArrayMap<Object, Registration<TRequest, TListener>> mListeners = + new ArrayMap<>(); + + @GuardedBy("mListeners") + @Nullable + private TRequest mMergedRequest; - public boolean addListener(@NonNull T listener, @NonNull Handler handler) + public boolean addListener(@NonNull TListener listener, @NonNull Handler handler) throws RemoteException { - return addInternal(listener, handler); + return addInternal(/* request= */ null, listener, handler); } - public boolean addListener(@NonNull T listener, @NonNull Executor executor) + public boolean addListener(@NonNull TListener listener, @NonNull Executor executor) throws RemoteException { - return addInternal(listener, executor); + return addInternal(/* request= */ null, listener, executor); } - protected final boolean addInternal(@NonNull Object listener, @NonNull Handler handler) - throws RemoteException { - return addInternal(listener, new HandlerExecutor(handler)); + public boolean addListener(@Nullable TRequest request, @NonNull TListener listener, + @NonNull Handler handler) throws RemoteException { + return addInternal(request, listener, handler); + } + + public boolean addListener(@Nullable TRequest request, @NonNull TListener listener, + @NonNull Executor executor) throws RemoteException { + return addInternal(request, listener, executor); + } + + protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener, + @NonNull Handler handler) throws RemoteException { + return addInternal(request, listener, new HandlerExecutor(handler)); } - protected final boolean addInternal(@NonNull Object listener, @NonNull Executor executor) + protected final boolean addInternal(@Nullable TRequest request, @NonNull Object listener, + @NonNull Executor executor) throws RemoteException { Preconditions.checkArgument(listener != null, "invalid null listener/callback"); - return addInternal(listener, new Registration<>(executor, convertKey(listener))); + return addInternal(listener, new Registration<>(request, executor, convertKey(listener))); } - private boolean addInternal(Object key, Registration<T> registration) throws RemoteException { + private boolean addInternal(Object key, Registration<TRequest, TListener> registration) + throws RemoteException { Preconditions.checkNotNull(registration); synchronized (mListeners) { - if (mListeners.isEmpty() && !registerService()) { - return false; - } - Registration<T> oldRegistration = mListeners.put(key, registration); + boolean initialRequest = mListeners.isEmpty(); + + Registration<TRequest, TListener> oldRegistration = mListeners.put(key, registration); if (oldRegistration != null) { oldRegistration.unregister(); } + TRequest merged = mergeRequests(); + + if (initialRequest || !Objects.equals(merged, mMergedRequest)) { + mMergedRequest = merged; + if (!initialRequest) { + unregisterService(); + } + registerService(mMergedRequest); + } + return true; } } public void removeListener(Object listener) throws RemoteException { synchronized (mListeners) { - Registration<T> oldRegistration = mListeners.remove(listener); + Registration<TRequest, TListener> oldRegistration = mListeners.remove(listener); if (oldRegistration == null) { return; } oldRegistration.unregister(); - if (mListeners.isEmpty()) { + boolean lastRequest = mListeners.isEmpty(); + TRequest merged = lastRequest ? null : mergeRequests(); + boolean newRequest = !lastRequest && !Objects.equals(merged, mMergedRequest); + + if (lastRequest || newRequest) { unregisterService(); + mMergedRequest = merged; + if (newRequest) { + registerService(mMergedRequest); + } } } } @SuppressWarnings("unchecked") - protected T convertKey(@NonNull Object listener) { - return (T) listener; + protected TListener convertKey(@NonNull Object listener) { + return (TListener) listener; } - protected abstract boolean registerService() throws RemoteException; + protected abstract boolean registerService(TRequest request) throws RemoteException; protected abstract void unregisterService() throws RemoteException; - protected void execute(Consumer<T> operation) { + @Nullable + protected TRequest merge(@NonNull TRequest[] requests) { + for (TRequest request : requests) { + Preconditions.checkArgument(request == null, + "merge() has to be overridden for non-null requests."); + } + return null; + } + + protected void execute(Consumer<TListener> operation) { synchronized (mListeners) { - for (Registration<T> registration : mListeners.values()) { + for (Registration<TRequest, TListener> registration : mListeners.values()) { registration.execute(operation); } } } + + @GuardedBy("mListeners") + @SuppressWarnings("unchecked") + @Nullable + private TRequest mergeRequests() { + Preconditions.checkState(Thread.holdsLock(mListeners)); + + if (mListeners.isEmpty()) { + return null; + } + + if (mListeners.size() == 1) { + return mListeners.valueAt(0).getRequest(); + } + + TRequest[] requests = (TRequest[]) new Object[mListeners.size()]; + for (int index = 0; index < mListeners.size(); index++) { + requests[index] = mListeners.valueAt(index).getRequest(); + } + return merge(requests); + } } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 6a5c0ec9457a..a99e68fbb7b6 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -22,6 +22,7 @@ import android.location.Criteria; import android.location.GeocoderParams; import android.location.Geofence; import android.location.GnssMeasurementCorrections; +import android.location.GnssRequest; import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; import android.location.IGnssStatusListener; @@ -69,8 +70,10 @@ interface ILocationManager double upperRightLatitude, double upperRightLongitude, int maxResults, in GeocoderParams params, out List<Address> addrs); - boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener, - String packageName, String featureId, String listenerIdentifier); + boolean addGnssMeasurementsListener(in GnssRequest request, + in IGnssMeasurementsListener listener, + String packageName, String featureId, + String listenerIdentifier); void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections, in String packageName); long getGnssCapabilities(in String packageName); @@ -107,6 +110,7 @@ interface ILocationManager boolean isProviderEnabledForUser(String provider, int userId); boolean isLocationEnabledForUser(int userId); + void setLocationEnabledForUser(boolean enabled, int userId); void addTestProvider(String name, in ProviderProperties properties, String opPackageName); void removeTestProvider(String provider, String opPackageName); void setTestProviderLocation(String provider, in Location loc, String opPackageName); diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 7e6486cc933e..8ae967fe79c2 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -478,13 +478,11 @@ public class LocationManager { @TestApi @RequiresPermission(WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean enabled, @NonNull UserHandle userHandle) { - Settings.Secure.putIntForUser( - mContext.getContentResolver(), - Settings.Secure.LOCATION_MODE, - enabled - ? Settings.Secure.LOCATION_MODE_ON - : Settings.Secure.LOCATION_MODE_OFF, - userHandle.getIdentifier()); + try { + mService.setLocationEnabledForUser(enabled, userHandle.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -2199,7 +2197,7 @@ public class LocationManager { * Registers a GNSS Measurement callback. * * @param request extra parameters to pass to GNSS measurement provider. For example, if {@link - * GnssRequest#isFullTrackingEnabled()} is true, GNSS chipset switches off duty + * GnssRequest#isFullTracking()} is true, GNSS chipset switches off duty * cycling. * @param executor the executor that the callback runs on. * @param callback a {@link GnssMeasurementsEvent.Callback} object to register. @@ -2216,7 +2214,12 @@ public class LocationManager { @NonNull GnssRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull GnssMeasurementsEvent.Callback callback) { - throw new RuntimeException(); + Preconditions.checkArgument(request != null, "invalid null request"); + try { + return mGnssMeasurementsListenerManager.addListener(request, callback, executor); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -2763,8 +2766,7 @@ public class LocationManager { } private class GnssStatusListenerManager extends - AbstractListenerManager<GnssStatus.Callback> { - + AbstractListenerManager<Void, GnssStatus.Callback> { @Nullable private IGnssStatusListener mListenerTransport; @@ -2782,19 +2784,19 @@ public class LocationManager { public boolean addListener(@NonNull GpsStatus.Listener listener, @NonNull Executor executor) throws RemoteException { - return addInternal(listener, executor); + return addInternal(null, listener, executor); } public boolean addListener(@NonNull OnNmeaMessageListener listener, @NonNull Handler handler) throws RemoteException { - return addInternal(listener, handler); + return addInternal(null, listener, handler); } public boolean addListener(@NonNull OnNmeaMessageListener listener, @NonNull Executor executor) throws RemoteException { - return addInternal(listener, executor); + return addInternal(null, listener, executor); } @Override @@ -2833,7 +2835,7 @@ public class LocationManager { } @Override - protected boolean registerService() throws RemoteException { + protected boolean registerService(Void ignored) throws RemoteException { Preconditions.checkState(mListenerTransport == null); GnssStatusListener transport = new GnssStatusListener(); @@ -2893,17 +2895,17 @@ public class LocationManager { } private class GnssMeasurementsListenerManager extends - AbstractListenerManager<GnssMeasurementsEvent.Callback> { + AbstractListenerManager<GnssRequest, GnssMeasurementsEvent.Callback> { @Nullable private IGnssMeasurementsListener mListenerTransport; @Override - protected boolean registerService() throws RemoteException { + protected boolean registerService(GnssRequest request) throws RemoteException { Preconditions.checkState(mListenerTransport == null); GnssMeasurementsListener transport = new GnssMeasurementsListener(); - if (mService.addGnssMeasurementsListener(transport, mContext.getPackageName(), + if (mService.addGnssMeasurementsListener(request, transport, mContext.getPackageName(), mContext.getFeatureId(), "gnss measurement callback")) { mListenerTransport = transport; return true; @@ -2920,6 +2922,18 @@ public class LocationManager { mListenerTransport = null; } + @Override + @Nullable + protected GnssRequest merge(@NonNull GnssRequest[] requests) { + Preconditions.checkArgument(requests.length > 0); + for (GnssRequest request : requests) { + if (request.isFullTracking()) { + return request; + } + } + return requests[0]; + } + private class GnssMeasurementsListener extends IGnssMeasurementsListener.Stub { @Override public void onGnssMeasurementsReceived(final GnssMeasurementsEvent event) { @@ -2934,13 +2948,13 @@ public class LocationManager { } private class GnssNavigationMessageListenerManager extends - AbstractListenerManager<GnssNavigationMessage.Callback> { + AbstractListenerManager<Void, GnssNavigationMessage.Callback> { @Nullable private IGnssNavigationMessageListener mListenerTransport; @Override - protected boolean registerService() throws RemoteException { + protected boolean registerService(Void ignored) throws RemoteException { Preconditions.checkState(mListenerTransport == null); GnssNavigationMessageListener transport = new GnssNavigationMessageListener(); @@ -2975,13 +2989,13 @@ public class LocationManager { } private class BatchedLocationCallbackManager extends - AbstractListenerManager<BatchedLocationCallback> { + AbstractListenerManager<Void, BatchedLocationCallback> { @Nullable private IBatchedLocationCallback mListenerTransport; @Override - protected boolean registerService() throws RemoteException { + protected boolean registerService(Void ignored) throws RemoteException { Preconditions.checkState(mListenerTransport == null); BatchedLocationCallback transport = new BatchedLocationCallback(); diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 3561f8393eea..9b183a3e0e92 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -244,7 +244,7 @@ public class Tuner implements AutoCloseable { * * <p> * Tuner events are started when {@link #tune(FrontendSettings)} is called and end when {@link - * #stopTune()} is called. + * #cancelTuning()} is called. * * @param eventListener receives tune events. * @throws SecurityException if the caller does not have appropriate permissions. @@ -309,7 +309,7 @@ public class Tuner implements AutoCloseable { */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result - public int stopTune() { + public int cancelTuning() { TunerUtils.checkTunerPermission(mContext); return nativeStopTune(); } @@ -322,8 +322,8 @@ public class Tuner implements AutoCloseable { * @param settings A {@link FrontendSettings} to configure the frontend. * @param scanType The scan type. * @throws SecurityException if the caller does not have appropriate permissions. - * @throws IllegalStateException if {@code scan} is called again before {@link #stopScan()} is - * called. + * @throws IllegalStateException if {@code scan} is called again before + * {@link #cancelScanning()} is called. */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result @@ -354,7 +354,7 @@ public class Tuner implements AutoCloseable { */ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) @Result - public int stopScan() { + public int cancelScanning() { TunerUtils.checkTunerPermission(mContext); int retVal = nativeStopScan(); mScanCallback = null; diff --git a/media/tests/TunerTest/OWNERS b/media/tests/TunerTest/OWNERS new file mode 100644 index 000000000000..73ea663aa37e --- /dev/null +++ b/media/tests/TunerTest/OWNERS @@ -0,0 +1,4 @@ +amyjojo@google.com +nchalko@google.com +quxiangfang@google.com +shubang@google.com diff --git a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java index bd5b79594c19..c4e41c87564f 100644 --- a/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java +++ b/packages/Incremental/NativeAdbDataLoader/src/com/android/incremental/nativeadb/NativeAdbDataLoaderService.java @@ -16,6 +16,8 @@ package com.android.incremental.nativeadb; +import android.annotation.NonNull; +import android.content.pm.DataLoaderParams; import android.service.dataloader.DataLoaderService; /** This code is used for testing only. */ @@ -26,7 +28,7 @@ public class NativeAdbDataLoaderService extends DataLoaderService { } @Override - public DataLoader onCreateDataLoader() { + public DataLoader onCreateDataLoader(@NonNull DataLoaderParams dataLoaderParams) { return null; } } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 804e0cb021b7..59881e7ba13d 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1069,17 +1069,17 @@ <string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> remaining</string> <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shutdown soon</string> + <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shut down soon</string> <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">Tablet may shutdown soon</string> + <string name="power_remaining_duration_only_shutdown_imminent" product="tablet">Tablet may shut down soon</string> <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device --> - <string name="power_remaining_duration_only_shutdown_imminent" product="device">Device may shutdown soon</string> + <string name="power_remaining_duration_only_shutdown_imminent" product="device">Device may shut down soon</string> <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="default">Phone may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string> + <string name="power_remaining_duration_shutdown_imminent" product="default">Phone may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="tablet">Tablet may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string> + <string name="power_remaining_duration_shutdown_imminent" product="tablet">Tablet may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> <!-- [CHAR_LIMIT=60] Label for battery level chart when shutdown is imminent--> - <string name="power_remaining_duration_shutdown_imminent" product="device">Device may shutdown soon (<xliff:g id="level">%1$s</xliff:g>)</string> + <string name="power_remaining_duration_shutdown_imminent" product="device">Device may shut down soon (<xliff:g id="level">%1$s</xliff:g>)</string> <!-- [CHAR_LIMIT=40] Label for battery level chart when charging --> <string name="power_charging"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="state">%2$s</xliff:g></string> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index 1ebe91736ba1..d03a747b743c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -216,12 +216,12 @@ public class A2dpProfile implements LocalBluetoothProfile { } public boolean supportsHighQualityAudio(BluetoothDevice device) { - int support = mService.supportsOptionalCodecs(device); + int support = mService.isOptionalCodecsSupported(device); return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED; } public boolean isHighQualityAudioEnabled(BluetoothDevice device) { - int enabled = mService.getOptionalCodecsEnabled(device); + int enabled = mService.isOptionalCodecsEnabled(device); if (enabled != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN) { return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED; } else if (getConnectionStatus(device) != BluetoothProfile.STATE_CONNECTED && diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java index b9081f241a91..b725ba5b8748 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java @@ -16,12 +16,9 @@ package com.android.settingslib.media; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; -import android.text.TextUtils; -import android.util.Log; import com.android.settingslib.R; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -62,46 +59,6 @@ public class InfoMediaDevice extends MediaDevice { return MediaDeviceUtils.getId(mRouteInfo); } - @Override - public void requestSetVolume(int volume) { - mRouterManager.requestSetVolume(mRouteInfo, volume); - } - - @Override - public int getMaxVolume() { - return mRouteInfo.getVolumeMax(); - } - - @Override - public int getCurrentVolume() { - return mRouteInfo.getVolume(); - } - - @Override - public String getClientPackageName() { - return mRouteInfo.getClientPackageName(); - } - - @Override - public String getClientAppLabel() { - final String packageName = mRouteInfo.getClientPackageName(); - if (TextUtils.isEmpty(packageName)) { - Log.d(TAG, "Client package name is empty"); - return mContext.getResources().getString(R.string.unknown); - } - try { - final PackageManager packageManager = mContext.getPackageManager(); - final String appLabel = packageManager.getApplicationLabel( - packageManager.getApplicationInfo(packageName, 0)).toString(); - if (!TextUtils.isEmpty(appLabel)) { - return appLabel; - } - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "unable to find " + packageName); - } - return mContext.getResources().getString(R.string.unknown); - } - public boolean isConnected() { return true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java index 580e086973d6..33c3d7e039b3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java @@ -16,14 +16,18 @@ package com.android.settingslib.media; import android.content.Context; +import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.text.TextUtils; +import android.util.Log; import androidx.annotation.IntDef; import androidx.annotation.VisibleForTesting; +import com.android.settingslib.R; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -113,7 +117,9 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * * @param volume is the new value. */ + public void requestSetVolume(int volume) { + mRouterManager.requestSetVolume(mRouteInfo, volume); } /** @@ -122,7 +128,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * @return max volume. */ public int getMaxVolume() { - return 100; + return mRouteInfo.getVolumeMax(); } /** @@ -131,7 +137,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * @return current volume. */ public int getCurrentVolume() { - return 0; + return mRouteInfo.getVolume(); } /** @@ -140,7 +146,7 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * @return package name. */ public String getClientPackageName() { - return null; + return mRouteInfo.getClientPackageName(); } /** @@ -149,7 +155,22 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { * @return application label. */ public String getClientAppLabel() { - return null; + final String packageName = mRouteInfo.getClientPackageName(); + if (TextUtils.isEmpty(packageName)) { + Log.d(TAG, "Client package name is empty"); + return mContext.getResources().getString(R.string.unknown); + } + try { + final PackageManager packageManager = mContext.getPackageManager(); + final String appLabel = packageManager.getApplicationLabel( + packageManager.getApplicationInfo(packageName, 0)).toString(); + if (!TextUtils.isEmpty(appLabel)) { + return appLabel; + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "unable to find " + packageName); + } + return mContext.getResources().getString(R.string.unknown); } /** diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java index c555cbec4bab..cb9092eba441 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java @@ -73,26 +73,26 @@ public class A2dpProfileTest { @Test public void supportsHighQualityAudio() { - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED); assertThat(mProfile.supportsHighQualityAudio(mDevice)).isTrue(); - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED); assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse(); - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_SUPPORT_UNKNOWN); assertThat(mProfile.supportsHighQualityAudio(mDevice)).isFalse(); } @Test public void isHighQualityAudioEnabled() { - when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsEnabled(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED); assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue(); - when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsEnabled(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED); assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse(); @@ -100,16 +100,16 @@ public class A2dpProfileTest { // then isHighQualityAudioEnabled() should return true or false based on whether optional // codecs are supported. If the device is connected then we should ask it directly, but if // the device isn't connected then rely on the stored pref about such support. - when(mBluetoothA2dp.getOptionalCodecsEnabled(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsEnabled(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN); when(mBluetoothA2dp.getConnectionState(any())).thenReturn( BluetoothProfile.STATE_DISCONNECTED); - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED); assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isFalse(); - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED); assertThat(mProfile.isHighQualityAudioEnabled(mDevice)).isTrue(); @@ -151,14 +151,14 @@ public class A2dpProfileTest { // Most tests want to simulate optional codecs being supported by the device, so do that // by default here. - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED); } @Test public void getLableCodecsNotSupported() { setupLabelTest(); - when(mBluetoothA2dp.supportsOptionalCodecs(any())).thenReturn( + when(mBluetoothA2dp.isOptionalCodecsSupported(any())).thenReturn( BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED); assertThat(mProfile.getHighQualityAudioOptionLabel(mDevice)).isEqualTo(UNKNOWN_CODEC_LABEL); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java index 04ceb2147c6e..77a67c286989 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java @@ -21,9 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageStats; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; @@ -36,14 +33,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.Shadows; -import org.robolectric.shadows.ShadowPackageManager; @RunWith(RobolectricTestRunner.class) public class InfoMediaDeviceTest { private static final String TEST_PACKAGE_NAME = "com.test.packagename"; - private static final String TEST_PACKAGE_NAME2 = "com.test.packagename2"; private static final String TEST_ID = "test_id"; private static final String TEST_NAME = "test_name"; @@ -52,27 +46,13 @@ public class InfoMediaDeviceTest { @Mock private MediaRoute2Info mRouteInfo; - private Context mContext; private InfoMediaDevice mInfoMediaDevice; - private ShadowPackageManager mShadowPackageManager; - private ApplicationInfo mAppInfo; - private PackageInfo mPackageInfo; - private PackageStats mPackageStats; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); - mAppInfo = new ApplicationInfo(); - mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED; - mAppInfo.packageName = TEST_PACKAGE_NAME; - mAppInfo.name = TEST_NAME; - mPackageInfo = new PackageInfo(); - mPackageInfo.packageName = TEST_PACKAGE_NAME; - mPackageInfo.applicationInfo = mAppInfo; - mPackageStats = new PackageStats(TEST_PACKAGE_NAME); mInfoMediaDevice = new InfoMediaDevice(mContext, mRouterManager, mRouteInfo, TEST_PACKAGE_NAME); @@ -106,32 +86,4 @@ public class InfoMediaDeviceTest { assertThat(mInfoMediaDevice.getId()).isEqualTo(TEST_ID); } - - @Test - public void getClientPackageName_returnPackageName() { - when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); - - assertThat(mInfoMediaDevice.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME); - } - - @Test - public void getClientAppLabel_matchedPackageName_returnLabel() { - when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); - - assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo( - mContext.getResources().getString(R.string.unknown)); - - mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); - - assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo(TEST_NAME); - } - - @Test - public void getClientAppLabel_noMatchedPackageName_returnDefault() { - mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); - when(mRouteInfo.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2); - - assertThat(mInfoMediaDevice.getClientAppLabel()).isEqualTo( - mContext.getResources().getString(R.string.unknown)); - } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index fb8b78b22055..3f29b72b978d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -23,9 +23,13 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageStats; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import com.android.settingslib.R; import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.HearingAidProfile; @@ -39,6 +43,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; import java.util.ArrayList; import java.util.Collections; @@ -58,6 +64,8 @@ public class MediaDeviceTest { private static final String ROUTER_ID_2 = "RouterId_2"; private static final String ROUTER_ID_3 = "RouterId_3"; private static final String TEST_PACKAGE_NAME = "com.test.playmusic"; + private static final String TEST_PACKAGE_NAME2 = "com.test.playmusic2"; + private static final String TEST_APPLICATION_LABEL = "playmusic"; private final BluetoothClass mHeadreeClass = new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES); private final BluetoothClass mCarkitClass = @@ -111,6 +119,10 @@ public class MediaDeviceTest { private InfoMediaDevice mInfoMediaDevice3; private List<MediaDevice> mMediaDevices = new ArrayList<>(); private PhoneMediaDevice mPhoneMediaDevice; + private ShadowPackageManager mShadowPackageManager; + private ApplicationInfo mAppInfo; + private PackageInfo mPackageInfo; + private PackageStats mPackageStats; @Before public void setUp() { @@ -394,4 +406,46 @@ public class MediaDeviceTest { verify(mMediaRouter2Manager).selectRoute(TEST_PACKAGE_NAME, mRouteInfo1); } + + @Test + public void getClientPackageName_returnPackageName() { + when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + assertThat(mInfoMediaDevice1.getClientPackageName()).isEqualTo(TEST_PACKAGE_NAME); + } + + private void initPackage() { + mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager()); + mAppInfo = new ApplicationInfo(); + mAppInfo.flags = ApplicationInfo.FLAG_INSTALLED; + mAppInfo.packageName = TEST_PACKAGE_NAME; + mAppInfo.name = TEST_APPLICATION_LABEL; + mPackageInfo = new PackageInfo(); + mPackageInfo.packageName = TEST_PACKAGE_NAME; + mPackageInfo.applicationInfo = mAppInfo; + mPackageStats = new PackageStats(TEST_PACKAGE_NAME); + } + + @Test + public void getClientAppLabel_matchedPackageName_returnLabel() { + initPackage(); + when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME); + + assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo( + mContext.getResources().getString(R.string.unknown)); + + mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); + + assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo(TEST_APPLICATION_LABEL); + } + + @Test + public void getClientAppLabel_noMatchedPackageName_returnDefault() { + initPackage(); + mShadowPackageManager.addPackage(mPackageInfo, mPackageStats); + when(mRouteInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME2); + + assertThat(mInfoMediaDevice1.getClientAppLabel()).isEqualTo( + mContext.getResources().getString(R.string.unknown)); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java index 61fdbd54ead1..ed308c8d6f6a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java @@ -120,9 +120,9 @@ public class PowerUtilTest { true /* basedOnUsage */); // additional battery percentage in this string - assertThat(info).isEqualTo("Phone may shutdown soon (10%)"); + assertThat(info).isEqualTo("Phone may shut down soon (10%)"); // shortened string should not have percentage - assertThat(info2).isEqualTo("Phone may shutdown soon"); + assertThat(info2).isEqualTo("Phone may shut down soon"); } @Test diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index c969bfd193b5..7b518a6167b7 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2193,7 +2193,7 @@ public class SettingsProvider extends ContentProvider { if (prefix == null) { return; } - String callingPackage = getCallingPackage(); + String callingPackage = resolveCallingPackage(); String namespace = prefix.replace("/", ""); if (DeviceConfig.getPublicNamespaces().contains(namespace)) { return; diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 1fe967b4750d..5458676e1061 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -646,16 +646,17 @@ android:label="Controls Providers" android:theme="@style/Theme.ControlsManagement" android:showForAllUsers="true" + android:clearTaskOnLaunch="true" android:excludeFromRecents="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> <activity android:name=".controls.management.ControlsFavoritingActivity" - android:parentActivityName=".controls.management.ControlsProviderSelectorActivity" android:theme="@style/Theme.ControlsManagement" android:excludeFromRecents="true" android:showForAllUsers="true" + android:finishOnTaskLaunch="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" android:visibleToInstantApps="true"> </activity> diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index 01811e9cdced..f607cc8f7e15 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -118,6 +118,7 @@ public interface QSTile { public CharSequence label; public CharSequence secondaryLabel; public CharSequence contentDescription; + public CharSequence stateDescription; public CharSequence dualLabelContentDescription; public boolean disabledByPolicy; public boolean dualTarget = false; @@ -135,6 +136,7 @@ public interface QSTile { || !Objects.equals(other.label, label) || !Objects.equals(other.secondaryLabel, secondaryLabel) || !Objects.equals(other.contentDescription, contentDescription) + || !Objects.equals(other.stateDescription, stateDescription) || !Objects.equals(other.dualLabelContentDescription, dualLabelContentDescription) || !Objects.equals(other.expandedAccessibilityClassName, @@ -151,6 +153,7 @@ public interface QSTile { other.label = label; other.secondaryLabel = secondaryLabel; other.contentDescription = contentDescription; + other.stateDescription = stateDescription; other.dualLabelContentDescription = dualLabelContentDescription; other.expandedAccessibilityClassName = expandedAccessibilityClassName; other.disabledByPolicy = disabledByPolicy; @@ -177,6 +180,7 @@ public interface QSTile { sb.append(",label=").append(label); sb.append(",secondaryLabel=").append(secondaryLabel); sb.append(",contentDescription=").append(contentDescription); + sb.append(",stateDescription=").append(stateDescription); sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription); sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName); sb.append(",disabledByPolicy=").append(disabledByPolicy); diff --git a/packages/SystemUI/res-keyguard/layout/controls_management.xml b/packages/SystemUI/res-keyguard/layout/controls_management.xml deleted file mode 100644 index 8330258e2456..000000000000 --- a/packages/SystemUI/res-keyguard/layout/controls_management.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center_horizontal" - android:paddingTop="@dimen/controls_management_top_padding" - android:paddingStart="@dimen/controls_management_side_padding" - android:paddingEnd="@dimen/controls_management_side_padding" > - - <TextView - android:id="@+id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceLarge" - android:textSize="@dimen/controls_title_size" - android:textAlignment="center" /> - - <TextView - android:id="@+id/subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/controls_management_titles_margin" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textAlignment="center" /> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/list" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/controls_management_list_margin" /> - -</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml new file mode 100644 index 000000000000..7b43a0315c10 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/controls_zone_header.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.Control.Title" + android:textColor="?android:attr/colorPrimary" + android:layout_marginStart="12dp" + android:layout_marginEnd="2dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="4dp"> + +</TextView>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_cancel_24.xml b/packages/SystemUI/res/drawable/ic_cancel_24.xml new file mode 100644 index 000000000000..8ab28ddb680d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_cancel_24.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_important.xml b/packages/SystemUI/res/drawable/ic_important.xml new file mode 100644 index 000000000000..d7439e167dd4 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_important.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M4,18.99h11c0.67,0 1.27,-0.32 1.63,-0.83L21,12l-4.37,-6.16C16.27,5.33 15.67,5 15,5H4l5,7 -5,6.99z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_important_outline.xml b/packages/SystemUI/res/drawable/ic_important_outline.xml new file mode 100644 index 000000000000..7a628bb65433 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_important_outline.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M15,19L3,19l4.5,-7L3,5h12c0.65,0 1.26,0.31 1.63,0.84L21,12l-4.37,6.16c-0.37,0.52 -0.98,0.84 -1.63,0.84zM6.5,17L15,17l3.5,-5L15,7L6.5,7l3.5,5 -3.5,5z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml deleted file mode 100644 index 4a731b35b423..000000000000 --- a/packages/SystemUI/res/drawable/ic_star.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="@android:color/white" - android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml deleted file mode 100644 index 9ede40be3b7b..000000000000 --- a/packages/SystemUI/res/drawable/ic_star_border.xml +++ /dev/null @@ -1,25 +0,0 @@ -<!-- - Copyright (C) 2020 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License - --> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - <path - android:fillColor="@android:color/white" - android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/screenshot_cancel.xml new file mode 100644 index 000000000000..be3c5983bb2e --- /dev/null +++ b/packages/SystemUI/res/drawable/screenshot_cancel.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:pathData="M24,24m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0" + android:fillColor="@android:color/white"/> + <path + android:fillColor="@color/GM2_grey_500" + android:pathData="M31,18.41L29.59,17 24,22.59 18.41,17 17,18.41 22.59,24 17,29.59 18.41,31 24,25.41 29.59,31 31,29.59 25.41,24z"/> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index 68c824698b2d..823bbcd6e68c 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -72,8 +72,8 @@ <CheckBox android:id="@+id/favorite" android:visibility="gone" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="48dp" + android:layout_height="48dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml new file mode 100644 index 000000000000..a7379bedebef --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal" + android:paddingTop="@dimen/controls_management_top_padding" + android:paddingStart="@dimen/controls_management_side_padding" + android:paddingEnd="@dimen/controls_management_side_padding" > + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textSize="@dimen/controls_title_size" + android:textAlignment="center" /> + + <TextView + android:id="@+id/subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/controls_management_titles_margin" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textAlignment="center" /> + + <androidx.core.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:orientation="vertical" + android:layout_marginTop="@dimen/controls_management_list_margin"> + + <ViewStub + android:id="@+id/stub" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + </androidx.core.widget.NestedScrollView> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="64dp"> + + <View + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="4dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:text="See other apps" + android:textAppearance="@style/TextAppearance.Control.Title" + android:textColor="?android:attr/colorPrimary" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent"/> + + <Button + android:id="@+id/done" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:text="Done" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent"/> + </androidx.constraintlayout.widget.ConstraintLayout> + </FrameLayout> + + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_management_apps.xml b/packages/SystemUI/res/layout/controls_management_apps.xml new file mode 100644 index 000000000000..2bab433d21f3 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management_apps.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<androidx.recyclerview.widget.RecyclerView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="match_parent" + > + +</androidx.recyclerview.widget.RecyclerView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_management_favorites.xml b/packages/SystemUI/res/layout/controls_management_favorites.xml new file mode 100644 index 000000000000..a36dd1247a04 --- /dev/null +++ b/packages/SystemUI/res/layout/controls_management_favorites.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <TextView + android:id="@+id/text_favorites" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="FAVORITES" + android:textAppearance="?android:attr/textAppearanceSmall" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@id/divider1" + app:layout_constraintTop_toTopOf="parent" + /> + + <View + android:id="@+id/divider1" + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/listFavorites" + app:layout_constraintTop_toBottomOf="@id/text_favorites" + /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/listFavorites" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:nestedScrollingEnabled="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/text_all" + app:layout_constraintTop_toBottomOf="@id/divider1"/> + + <TextView + android:id="@+id/text_all" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:text="ALL" + android:textAppearance="?android:attr/textAppearanceSmall" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@id/divider2" + app:layout_constraintTop_toBottomOf="@id/listFavorites" + /> + + <View + android:id="@+id/divider2" + android:layout_width="match_parent" + android:layout_height="@dimen/controls_app_divider_height" + android:layout_gravity="center_horizontal|top" + android:background="?android:attr/listDivider" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@id/listAll" + app:layout_constraintTop_toBottomOf="@id/text_all" + /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/listAll" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="@dimen/controls_management_list_margin" + android:nestedScrollingEnabled="false" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/divider2"/> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml index 40b2476941c2..2cd9505b8fe4 100644 --- a/packages/SystemUI/res/layout/controls_with_favorites.xml +++ b/packages/SystemUI/res/layout/controls_with_favorites.xml @@ -1,3 +1,18 @@ +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 1f7def2bc956..d0151fff95c4 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -55,10 +55,22 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:elevation="8dp" + android:elevation="@dimen/screenshot_preview_elevation" android:visibility="gone" android:background="@drawable/screenshot_rounded_corners" android:adjustViewBounds="true"/> + <FrameLayout + android:id="@+id/global_screenshot_dismiss_button" + android:layout_width="@dimen/screenshot_dismiss_button_tappable_size" + android:layout_height="@dimen/screenshot_dismiss_button_tappable_size" + android:elevation="9dp" + android:visibility="gone"> + <ImageView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="@dimen/screenshot_dismiss_button_margin" + android:src="@drawable/screenshot_cancel"/> + </FrameLayout> <ImageView android:id="@+id/global_screenshot_flash" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml index a9d6e3575317..84606126086d 100644 --- a/packages/SystemUI/res/layout/notification_conversation_info.xml +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -28,7 +28,7 @@ android:paddingStart="@*android:dimen/notification_content_margin_start"> <!-- Package Info --> - <RelativeLayout + <LinearLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="@dimen/notification_guts_conversation_header_height" @@ -41,16 +41,20 @@ android:layout_height="@dimen/notification_guts_conversation_icon_size" android:layout_centerVertical="true" android:layout_alignParentStart="true" - android:layout_marginEnd="6dp" /> + android:layout_marginEnd="15dp" /> <LinearLayout android:id="@+id/names" + android:layout_weight="1" + android:layout_width="0dp" android:orientation="vertical" - android:layout_width="wrap_content" + android:layout_height="wrap_content" android:minHeight="@dimen/notification_guts_conversation_icon_size" android:layout_centerVertical="true" android:gravity="center_vertical" - android:layout_toEndOf="@id/conversation_icon"> + android:layout_alignEnd="@id/conversation_icon" + android:layout_toEndOf="@id/conversation_icon" + android:layout_alignStart="@id/mute"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" @@ -107,67 +111,40 @@ android:layout_weight="1" style="@style/TextAppearance.NotificationImportanceChannel"/> </LinearLayout> + <TextView + android:id="@+id/delegate_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + style="@style/TextAppearance.NotificationImportanceHeader" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:ellipsize="end" + android:text="@string/notification_delegate_header" + android:layout_toEndOf="@id/pkg_divider" + android:maxLines="1" /> </LinearLayout> - <TextView - android:id="@+id/pkg_divider" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - style="@style/TextAppearance.NotificationImportanceHeader" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:layout_toEndOf="@id/name" - android:text="@*android:string/notification_header_divider_symbol" /> - <TextView - android:id="@+id/delegate_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_centerVertical="true" - style="@style/TextAppearance.NotificationImportanceHeader" - android:layout_marginStart="2dp" - android:layout_marginEnd="2dp" - android:ellipsize="end" - android:text="@string/notification_delegate_header" - android:layout_toEndOf="@id/pkg_divider" - android:maxLines="1" /> - <!-- end aligned fields --> <ImageButton - android:id="@+id/demote" - android:layout_width="@dimen/notification_importance_toggle_size" - android:layout_height="@dimen/notification_importance_toggle_size" - android:layout_centerVertical="true" - android:background="@drawable/ripple_drawable" - android:contentDescription="@string/demote" - android:src="@drawable/ic_demote_conversation" - android:layout_toStartOf="@id/app_settings" - android:tint="@color/notification_guts_link_icon_tint"/> - <!-- Optional link to app. Only appears if the channel is not disabled and the app -asked for it --> - <ImageButton - android:id="@+id/app_settings" + android:id="@+id/mute" android:layout_width="@dimen/notification_importance_toggle_size" android:layout_height="@dimen/notification_importance_toggle_size" android:layout_centerVertical="true" - android:visibility="gone" android:background="@drawable/ripple_drawable" - android:contentDescription="@string/notification_app_settings" - android:src="@drawable/ic_info" - android:layout_toStartOf="@id/info" + android:layout_toStartOf="@id/fave" android:tint="@color/notification_guts_link_icon_tint"/> <ImageButton - android:id="@+id/info" + android:id="@+id/fave" android:layout_width="@dimen/notification_importance_toggle_size" android:layout_height="@dimen/notification_importance_toggle_size" android:layout_centerVertical="true" android:background="@drawable/ripple_drawable" - android:contentDescription="@string/notification_more_settings" - android:src="@drawable/ic_settings" android:layout_alignParentEnd="true" android:tint="@color/notification_guts_link_icon_tint"/> - </RelativeLayout> + + </LinearLayout> <LinearLayout android:id="@+id/actions" @@ -182,29 +159,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> - <Button - android:id="@+id/bubble" - android:layout_height="@dimen/notification_guts_conversation_action_height" - android:layout_width="match_parent" - style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_favorite" - android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_create_bubble" - android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" - android:drawableTint="@color/notification_guts_link_icon_tint"/> - <View - android:layout_width="match_parent" - android:layout_height="0.5dp" - android:background="@color/GM2_grey_300" /> <Button - android:id="@+id/home" + android:id="@+id/snooze" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_home_screen" + android:text="@string/notification_menu_snooze_action" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_add_to_home" + android:drawableStart="@drawable/ic_snooze" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -212,12 +175,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> + <Button - android:id="@+id/fave" + android:id="@+id/bubble" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" + android:text="@string/notification_conversation_favorite" android:gravity="left|center_vertical" + android:drawableStart="@drawable/ic_create_bubble" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -226,13 +192,13 @@ asked for it --> android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> <Button - android:id="@+id/snooze" + android:id="@+id/home" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_menu_snooze_action" + android:text="@string/notification_conversation_home_screen" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_snooze" + android:drawableStart="@drawable/ic_add_to_home" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> @@ -240,14 +206,15 @@ asked for it --> android:layout_width="match_parent" android:layout_height="0.5dp" android:background="@color/GM2_grey_300" /> + <Button - android:id="@+id/mute" + android:id="@+id/info" android:layout_height="@dimen/notification_guts_conversation_action_height" android:layout_width="match_parent" style="?android:attr/borderlessButtonStyle" - android:text="@string/notification_conversation_mute" + android:drawableStart="@drawable/ic_settings" + android:text="@string/notification_menu_settings_action" android:gravity="left|center_vertical" - android:drawableStart="@drawable/ic_notifications_silence" android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start" android:drawableTint="@color/notification_guts_link_icon_tint"/> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7a3395c229a0..f0e4e53dfc53 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -103,7 +103,8 @@ <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item> <dimen name="group_overflow_number_size">@*android:dimen/notification_text_size</dimen> - <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end</dimen> + <dimen name="group_overflow_number_padding">@*android:dimen/notification_content_margin_end + </dimen> <!-- max height of a notification such that the content can still fade out when closing --> <dimen name="max_notification_fadeout_height">100dp</dimen> @@ -267,7 +268,9 @@ <dimen name="status_bar_icon_drawing_size">15dp</dimen> <!-- size at which Notification icons will be drawn on Ambient Display --> - <dimen name="status_bar_icon_drawing_size_dark">@*android:dimen/notification_header_icon_size_ambient</dimen> + <dimen name="status_bar_icon_drawing_size_dark"> + @*android:dimen/notification_header_icon_size_ambient + </dimen> <!-- size of notification icons when the notifications are hidden --> <dimen name="hidden_shelf_icon_size">16dp</dimen> @@ -299,8 +302,11 @@ <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen> <dimen name="global_screenshot_bg_padding">20dp</dimen> <dimen name="global_screenshot_x_scale">80dp</dimen> + <dimen name="screenshot_preview_elevation">8dp</dimen> <dimen name="screenshot_offset_y">48dp</dimen> <dimen name="screenshot_offset_x">16dp</dimen> + <dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen> + <dimen name="screenshot_dismiss_button_margin">8dp</dimen> <dimen name="screenshot_action_container_offset_y">32dp</dimen> <dimen name="screenshot_action_container_corner_radius">10dp</dimen> <dimen name="screenshot_action_container_padding_vertical">10dp</dimen> @@ -597,10 +603,14 @@ <!-- The height of the divider between the individual notifications in a notification group. --> - <dimen name="notification_children_container_divider_height">@dimen/notification_divider_height</dimen> + <dimen name="notification_children_container_divider_height"> + @dimen/notification_divider_height + </dimen> <!-- The top margin for the notification children container in its non-expanded form. --> - <dimen name="notification_children_container_margin_top">@*android:dimen/notification_content_margin_top</dimen> + <dimen name="notification_children_container_margin_top"> + @*android:dimen/notification_content_margin_top + </dimen> <!-- The height of a notification header --> <dimen name="notification_header_height">53dp</dimen> @@ -967,6 +977,9 @@ Equal to pip_action_size - pip_action_padding. --> <dimen name="pip_expand_container_edge_margin">30dp</dimen> + <!-- The touchable/draggable edge size for PIP resize. --> + <dimen name="pip_resize_edge_size">30dp</dimen> + <dimen name="default_gear_space">18dp</dimen> <dimen name="cell_overlay_padding">18dp</dimen> @@ -1061,9 +1074,12 @@ <integer name="wireless_charging_scale_dots_duration">83</integer> <integer name="wireless_charging_num_dots">16</integer> <!-- Starting text size in sp of batteryLevel for wireless charging animation --> - <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen">0</item> + <item name="wireless_charging_anim_battery_level_text_size_start" format="float" type="dimen"> + 0 + </item> <!-- Ending text size in sp of batteryLevel for wireless charging animation --> - <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24</item> + <item name="wireless_charging_anim_battery_level_text_size_end" format="float" type="dimen">24 + </item> <!-- time until battery info is at full opacity--> <integer name="wireless_charging_anim_opacity_offset">80</integer> <!-- duration batteryLevel opacity goes from 0 to 1 duration --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index b85b51e4f2cc..8f9e934c97e5 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1829,23 +1829,23 @@ <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification --> <string name="demote">Mark this notification as not a conversation</string> - <!-- [CHAR LIMIT=100] Mark this conversation as a favorite --> - <string name="notification_conversation_favorite">Mark as important</string> + <!-- [CHAR LIMIT=100] This conversation is marked as important --> + <string name="notification_conversation_favorite">Important conversation</string> - <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite --> - <string name="notification_conversation_unfavorite">Mark as unimportant</string> + <!-- [CHAR LIMIT=100] This conversation is not marked as important --> + <string name="notification_conversation_unfavorite">Not an important conversation</string> - <!-- [CHAR LIMIT=100] Mute this conversation --> - <string name="notification_conversation_mute">Silence</string> + <!-- [CHAR LIMIT=100] This conversation is silenced (will not make sound or vibrate)--> + <string name="notification_conversation_mute">Silenced</string> - <!-- [CHAR LIMIT=100] Umute this conversation --> + <!-- [CHAR LIMIT=100] This conversation is alerting (may make sound and/or vibrate)--> <string name="notification_conversation_unmute">Alerting</string> <!-- [CHAR LIMIT=100] Show notification as bubble --> - <string name="notification_conversation_bubble">Show as bubble</string> + <string name="notification_conversation_bubble">Show bubble</string> <!-- [CHAR LIMIT=100] Turn off bubbles for notification --> - <string name="notification_conversation_unbubble">Turn off bubbles</string> + <string name="notification_conversation_unbubble">Remove bubbles</string> <!-- [CHAR LIMIT=100] Add this conversation to home screen --> <string name="notification_conversation_home_screen">Add to home screen</string> @@ -1860,7 +1860,10 @@ <string name="notification_menu_snooze_description">notification snooze options</string> <!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] --> - <string name="notification_menu_snooze_action">Snooze</string> + <string name="notification_menu_snooze_action">Remind me</string> + + <!-- Notification: Menu row: Label for the snooze action shown in local context menu. [CHAR LIMIT=NONE] --> + <string name="notification_menu_settings_action">Settings</string> <!-- Notification: Snooze panel: Snooze undo button label. [CHAR LIMIT=50]--> <string name="snooze_undo">UNDO</string> @@ -2029,6 +2032,9 @@ <!-- Label for feature switch [CHAR LIMIT=30] --> <string name="switch_bar_off">Off</string> + <!-- The tile in quick settings is unavailable. [CHAR LIMIT=32] --> + <string name="tile_unavailable">Unavailable</string> + <!-- SysUI Tuner: Button that leads to the navigation bar customization screen [CHAR LIMIT=60] --> <string name="nav_bar">Navigation bar</string> @@ -2583,6 +2589,4 @@ <string name="controls_favorite_default_title">Controls</string> <!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] --> <string name="controls_favorite_subtitle">Choose controls for quick access</string> - - </resources> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index bea55c820b40..2d55a1ddf654 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -146,7 +146,6 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V int viewType) { BadgedImageView view = (BadgedImageView) LayoutInflater.from(parent.getContext()) .inflate(R.layout.bubble_view, parent, false); - view.setPadding(15, 15, 15, 15); return new ViewHolder(view); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt index 53841e2f144b..49a16d892ef4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt @@ -20,6 +20,6 @@ import android.service.controls.Control data class ControlStatus( val control: Control, - val favorite: Boolean, + var favorite: Boolean, val removed: Boolean = false )
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index b3ba2b22f6df..fce504120b62 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -26,10 +26,18 @@ interface ControlsController : UserAwareController { val available: Boolean fun getFavoriteControls(): List<ControlInfo> - fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit) + fun loadForComponent( + componentName: ComponentName, + callback: (List<ControlStatus>, List<String>) -> Unit + ) + fun subscribeToFavorites() fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) - fun countFavoritesForComponent(componentName: ComponentName): Int = 0 + fun replaceFavoritesForComponent(componentName: ComponentName, favorites: List<ControlInfo>) + + fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo> + fun countFavoritesForComponent(componentName: ComponentName): Int + fun unsubscribe() fun action(controlInfo: ControlInfo, action: ControlAction) fun refreshStatus(componentName: ComponentName, control: Control) diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 7de1557ebc65..e611197b78ae 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -70,9 +70,10 @@ class ControlsControllerImpl @Inject constructor ( } // Map of map: ComponentName -> (String -> ControlInfo). - // Only for current user + // @GuardedBy("currentFavorites") - private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>() + private val currentFavorites = ArrayMap<ComponentName, MutableList<ControlInfo>>() + .withDefault { mutableListOf() } private var userChanging: Boolean = true @@ -180,15 +181,14 @@ class ControlsControllerImpl @Inject constructor ( val infos = persistenceWrapper.readFavorites() synchronized(currentFavorites) { infos.forEach { - currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() }) - .put(it.controlId, it) + currentFavorites.getOrPut(it.component, { mutableListOf() }).add(it) } } } override fun loadForComponent( componentName: ComponentName, - callback: (List<ControlStatus>) -> Unit + callback: (List<ControlStatus>, List<String>) -> Unit ) { if (!confirmAvailability()) { if (userChanging) { @@ -200,29 +200,34 @@ class ControlsControllerImpl @Inject constructor ( TimeUnit.MILLISECONDS ) } else { - callback(emptyList()) + callback(emptyList(), emptyList()) } return } bindingController.bindAndLoad(componentName) { synchronized(currentFavorites) { - val favoritesForComponentKeys: Set<String> = - currentFavorites.get(componentName)?.keys ?: emptySet() - val changed = updateFavoritesLocked(componentName, it) + val favoritesForComponentKeys: List<String> = + currentFavorites.getValue(componentName).map { it.controlId } + val changed = updateFavoritesLocked(componentName, it, favoritesForComponentKeys) if (changed) { persistenceWrapper.storeFavorites(favoritesAsListLocked()) } - val removed = findRemovedLocked(favoritesForComponentKeys, it) - callback(removed.map { currentFavorites.getValue(componentName).getValue(it) } - .map(::createRemovedStatus) + - it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) }) + val removed = findRemovedLocked(favoritesForComponentKeys.toSet(), it) + val controlsWithFavorite = + it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) } + callback( + currentFavorites.getValue(componentName) + .filter { it.controlId in removed } + .map(::createRemovedStatus) + controlsWithFavorite, + favoritesForComponentKeys + ) } } } private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus { val intent = Intent(context, ControlsFavoritingActivity::class.java).apply { - putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component) + putExtra(Intent.EXTRA_COMPONENT_NAME, controlInfo.component) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP } val pendingIntent = PendingIntent.getActivity(context, @@ -243,17 +248,24 @@ class ControlsControllerImpl @Inject constructor ( } @GuardedBy("currentFavorites") - private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean { - val favorites = currentFavorites.get(componentName) ?: mutableMapOf() - val favoriteKeys = favorites.keys + private fun updateFavoritesLocked( + componentName: ComponentName, + list: List<Control>, + favoriteKeys: List<String> + ): Boolean { + val favorites = currentFavorites.get(componentName) ?: mutableListOf() if (favoriteKeys.isEmpty()) return false // early return var changed = false - list.forEach { - if (it.controlId in favoriteKeys) { - val value = favorites.getValue(it.controlId) - if (value.controlTitle != it.title || value.deviceType != it.deviceType) { - favorites[it.controlId] = value.copy(controlTitle = it.title, - deviceType = it.deviceType) + list.forEach { control -> + if (control.controlId in favoriteKeys) { + val index = favorites.indexOfFirst { it.controlId == control.controlId } + val value = favorites[index] + if (value.controlTitle != control.title || + value.deviceType != control.deviceType) { + favorites[index] = value.copy( + controlTitle = control.title, + deviceType = control.deviceType + ) changed = true } } @@ -263,14 +275,14 @@ class ControlsControllerImpl @Inject constructor ( @GuardedBy("currentFavorites") private fun favoritesAsListLocked(): List<ControlInfo> { - return currentFavorites.flatMap { it.value.values } + return currentFavorites.flatMap { it.value } } override fun subscribeToFavorites() { if (!confirmAvailability()) return // Make a copy of the favorites list val favorites = synchronized(currentFavorites) { - currentFavorites.flatMap { it.value.values.toList() } + currentFavorites.flatMap { it.value } } bindingController.subscribe(favorites) } @@ -286,22 +298,19 @@ class ControlsControllerImpl @Inject constructor ( val listOfControls = synchronized(currentFavorites) { if (state) { if (controlInfo.component !in currentFavorites) { - currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>()) + currentFavorites.put(controlInfo.component, mutableListOf()) changed = true } val controlsForComponent = currentFavorites.getValue(controlInfo.component) - if (controlInfo.controlId !in controlsForComponent) { - controlsForComponent.put(controlInfo.controlId, controlInfo) + if (controlsForComponent.firstOrNull { + it.controlId == controlInfo.controlId + } == null) { + controlsForComponent.add(controlInfo) changed = true - } else { - if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) { - controlsForComponent.put(controlInfo.controlId, controlInfo) - changed = true - } } } else { changed = currentFavorites.get(controlInfo.component) - ?.remove(controlInfo.controlId) != null + ?.remove(controlInfo) != null } favoritesAsListLocked() } @@ -310,6 +319,19 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun replaceFavoritesForComponent( + componentName: ComponentName, + favorites: List<ControlInfo> + ) { + if (!confirmAvailability()) return + val filtered = favorites.filter { it.component == componentName } + val listOfControls = synchronized(currentFavorites) { + currentFavorites.put(componentName, filtered.toMutableList()) + favoritesAsListLocked() + } + persistenceWrapper.storeFavorites(listOfControls) + } + override fun refreshStatus(componentName: ComponentName, control: Control) { if (!confirmAvailability()) { Log.d(TAG, "Controls not available") @@ -317,7 +339,13 @@ class ControlsControllerImpl @Inject constructor ( } executor.execute { synchronized(currentFavorites) { - val changed = updateFavoritesLocked(componentName, listOf(control)) + val favoriteKeysForComponent = + currentFavorites.get(componentName)?.map { it.controlId } ?: emptyList() + val changed = updateFavoritesLocked( + componentName, + listOf(control), + favoriteKeysForComponent + ) if (changed) { persistenceWrapper.storeFavorites(favoritesAsListLocked()) } @@ -361,6 +389,12 @@ class ControlsControllerImpl @Inject constructor ( } } + override fun getFavoritesForComponent(componentName: ComponentName): List<ControlInfo> { + return synchronized(currentFavorites) { + currentFavorites.get(componentName) ?: emptyList() + } + } + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { pw.println("ControlsController state:") pw.println(" Available: $available") @@ -368,10 +402,8 @@ class ControlsControllerImpl @Inject constructor ( pw.println(" Current user: ${currentUser.identifier}") pw.println(" Favorites:") synchronized(currentFavorites) { - currentFavorites.forEach { - it.value.forEach { - pw.println(" ${it.value}") - } + favoritesAsListLocked().forEach { + pw.println(" ${ it }") } } pw.println(bindingController.toString()) diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt index b12243964fc1..89caaceebb5c 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt @@ -29,6 +29,7 @@ import androidx.recyclerview.widget.RecyclerView import com.android.settingslib.applications.DefaultAppInfo import com.android.settingslib.widget.CandidateInfo import com.android.systemui.R +import java.text.Collator import java.util.concurrent.Executor /** @@ -44,23 +45,27 @@ import java.util.concurrent.Executor * @param onAppSelected a callback to indicate that an app has been selected in the list. */ class AppAdapter( + backgroundExecutor: Executor, uiExecutor: Executor, lifecycle: Lifecycle, controlsListingController: ControlsListingController, private val layoutInflater: LayoutInflater, private val onAppSelected: (ComponentName?) -> Unit = {}, - private val favoritesRenderer: FavoritesRenderer + private val favoritesRenderer: FavoritesRenderer, + private val resources: Resources ) : RecyclerView.Adapter<AppAdapter.Holder>() { private var listOfServices = emptyList<CandidateInfo>() private val callback = object : ControlsListingController.ControlsListingCallback { override fun onServicesUpdated(list: List<CandidateInfo>) { - uiExecutor.execute { - listOfServices = list.sortedBy { - it.loadLabel().toString() + backgroundExecutor.execute { + val collator = Collator.getInstance(resources.getConfiguration().locale) + val localeComparator = compareBy<CandidateInfo, CharSequence>(collator) { + it.loadLabel() } - notifyDataSetChanged() + listOfServices = list.sortedWith(localeComparator) + uiExecutor.execute(::notifyDataSetChanged) } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index 65dcc2b193d5..d3cabe67790e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -25,101 +25,150 @@ import android.view.ViewGroup import android.widget.CheckBox import android.widget.ImageView import android.widget.TextView +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R -import com.android.systemui.controls.ControlStatus -import com.android.systemui.controls.controller.ControlInfo import com.android.systemui.controls.ui.RenderInfo +private typealias ModelFavoriteChanger = (String, Boolean) -> Unit + /** * Adapter for binding [Control] information to views. * + * The model for this adapter is provided by a [FavoriteModel] that is set using + * [changeFavoritesModel]. This allows for updating the model if there's a reload. + * * @param layoutInflater an inflater for the views in the containing [RecyclerView] - * @param favoriteCallback a callback to be called when the favorite status of a [Control] is - * changed. The callback will take a [ControlInfo.Builder] that's - * pre-populated with the [Control] information and the new favorite - * status. + * @param onlyFavorites set to true to only display favorites instead of all controls */ class ControlAdapter( private val layoutInflater: LayoutInflater, - private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit -) : RecyclerView.Adapter<ControlAdapter.Holder>() { + private val onlyFavorites: Boolean = false +) : RecyclerView.Adapter<Holder>() { + + companion object { + private const val TYPE_ZONE = 0 + private const val TYPE_CONTROL = 1 + } + + val spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return if (getItemViewType(position) == TYPE_ZONE) 2 else 1 + } + } - var listOfControls = emptyList<ControlStatus>() + var modelList: List<ElementWrapper> = emptyList() + private var favoritesModel: FavoriteModel? = null - override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder { - return Holder(layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply { - layoutParams.apply { - width = ViewGroup.LayoutParams.MATCH_PARENT + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { + return when (viewType) { + TYPE_CONTROL -> { + ControlHolder( + layoutInflater.inflate(R.layout.controls_base_item, parent, false).apply { + layoutParams.apply { + width = ViewGroup.LayoutParams.MATCH_PARENT + } + elevation = 15f + }, + { id, favorite -> + favoritesModel?.changeFavoriteStatus(id, favorite) + }) + } + TYPE_ZONE -> { + ZoneHolder(layoutInflater.inflate(R.layout.controls_zone_header, parent, false)) } - elevation = 15f - }) + else -> throw IllegalStateException("Wrong viewType: $viewType") + } } - override fun getItemCount() = listOfControls.size + fun changeFavoritesModel(favoritesModel: FavoriteModel) { + this.favoritesModel = favoritesModel + if (onlyFavorites) { + modelList = favoritesModel.favorites + } else { + modelList = favoritesModel.all + } + notifyDataSetChanged() + } + + override fun getItemCount() = modelList.size override fun onBindViewHolder(holder: Holder, index: Int) { - holder.bindData(listOfControls[index], favoriteCallback) + holder.bindData(modelList[index]) } + override fun getItemViewType(position: Int): Int { + return when (modelList[position]) { + is ZoneNameWrapper -> TYPE_ZONE + is ControlWrapper -> TYPE_CONTROL + } + } +} + +/** + * Holder for binding views in the [RecyclerView]- + * @param view the [View] for this [Holder] + */ +sealed class Holder(view: View) : RecyclerView.ViewHolder(view) { + /** - * Holder for binding views in the [RecyclerView]- + * Bind the data from the model into the view */ - class Holder(view: View) : RecyclerView.ViewHolder(view) { - private val icon: ImageView = itemView.requireViewById(R.id.icon) - private val title: TextView = itemView.requireViewById(R.id.title) - private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) - private val removed: TextView = itemView.requireViewById(R.id.status) - private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply { - visibility = View.VISIBLE - } + abstract fun bindData(wrapper: ElementWrapper) +} - /** - * Bind data to the view - * @param data information about the [Control] - * @param callback a callback to be called when the favorite status of the [Control] is - * changed. The callback will take a [ControlInfo.Builder] that's - * pre-populated with the [Control] information and the new favorite status. - */ - fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) { - val renderInfo = getRenderInfo(data.control.deviceType, data.favorite) - title.text = data.control.title - subtitle.text = data.control.subtitle - favorite.isChecked = data.favorite - removed.text = if (data.removed) "Removed" else "" - favorite.setOnClickListener { - val infoBuilder = ControlInfo.Builder().apply { - controlId = data.control.controlId - controlTitle = data.control.title - deviceType = data.control.deviceType - } - callback(infoBuilder, favorite.isChecked) - } - itemView.setOnClickListener { - favorite.performClick() - } - applyRenderInfo(renderInfo) - } +/** + * Holder for using with [ZoneNameWrapper] to display names of zones. + */ +private class ZoneHolder(view: View) : Holder(view) { + private val zone: TextView = itemView as TextView - private fun getRenderInfo( - @DeviceTypes.DeviceType deviceType: Int, - favorite: Boolean - ): RenderInfo { - return RenderInfo.lookup(deviceType, favorite) - } + override fun bindData(wrapper: ElementWrapper) { + wrapper as ZoneNameWrapper + zone.text = wrapper.zoneName + } +} - private fun applyRenderInfo(ri: RenderInfo) { - val context = itemView.context - val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) +/** + * Holder for using with [ControlWrapper] to display names of zones. + * @param favoriteCallback this callback will be called whenever the favorite state of the + * [Control] this view represents changes. + */ +private class ControlHolder(view: View, val favoriteCallback: ModelFavoriteChanger) : Holder(view) { + private val icon: ImageView = itemView.requireViewById(R.id.icon) + private val title: TextView = itemView.requireViewById(R.id.title) + private val subtitle: TextView = itemView.requireViewById(R.id.subtitle) + private val removed: TextView = itemView.requireViewById(R.id.status) + private val favorite: CheckBox = itemView.requireViewById<CheckBox>(R.id.favorite).apply { + visibility = View.VISIBLE + } - icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId)) - icon.setImageTintList(fg) + override fun bindData(wrapper: ElementWrapper) { + wrapper as ControlWrapper + val data = wrapper.controlStatus + val renderInfo = getRenderInfo(data.control.deviceType) + title.text = data.control.title + subtitle.text = data.control.subtitle + favorite.isChecked = data.favorite + removed.text = if (data.removed) "Removed" else "" + favorite.setOnClickListener { + favoriteCallback(data.control.controlId, favorite.isChecked) } + applyRenderInfo(renderInfo) } - fun setItems(list: List<ControlStatus>) { - listOfControls = list - notifyDataSetChanged() + private fun getRenderInfo( + @DeviceTypes.DeviceType deviceType: Int + ): RenderInfo { + return RenderInfo.lookup(deviceType, true) + } + + private fun applyRenderInfo(ri: RenderInfo) { + val context = itemView.context + val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme()) + + icon.setImageIcon(Icon.createWithResource(context, ri.iconResourceId)) + icon.setImageTintList(fg) } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index be5258344492..1e5237148188 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -18,10 +18,14 @@ package com.android.systemui.controls.management import android.app.Activity import android.content.ComponentName +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater +import android.view.ViewStub +import android.widget.Button import android.widget.TextView import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher @@ -41,13 +45,36 @@ class ControlsFavoritingActivity @Inject constructor( companion object { private const val TAG = "ControlsFavoritingActivity" const val EXTRA_APP = "extra_app_label" - const val EXTRA_COMPONENT = "extra_component" } - private lateinit var recyclerView: RecyclerView - private lateinit var adapter: ControlAdapter + private lateinit var recyclerViewAll: RecyclerView + private lateinit var adapterAll: ControlAdapter + private lateinit var recyclerViewFavorites: RecyclerView + private lateinit var adapterFavorites: ControlAdapter private var component: ComponentName? = null + private var currentModel: FavoriteModel? = null + private var itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback( + /* dragDirs */ ItemTouchHelper.UP + or ItemTouchHelper.DOWN + or ItemTouchHelper.LEFT + or ItemTouchHelper.RIGHT, + /* swipeDirs */0 + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return currentModel?.onMoveItem( + viewHolder.adapterPosition, target.adapterPosition) != null + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {} + + override fun isItemViewSwipeEnabled() = false + } + private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = controller.currentUserId @@ -62,41 +89,77 @@ class ControlsFavoritingActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.controls_management) + requireViewById<ViewStub>(R.id.stub).apply { + layoutResource = R.layout.controls_management_favorites + inflate() + } val app = intent.getCharSequenceExtra(EXTRA_APP) - component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT) - - // If we have no component name, there's not much we can do. - val callback = component?.let { - { infoBuilder: ControlInfo.Builder, status: Boolean -> - infoBuilder.componentName = it - controller.changeFavoriteStatus(infoBuilder.build(), status) - } - } ?: { _, _ -> Unit } + component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) - recyclerView = requireViewById(R.id.list) - adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback) - recyclerView.adapter = adapter - recyclerView.layoutManager = GridLayoutManager(applicationContext, 2) - val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin) - recyclerView.addItemDecoration(MarginItemDecorator(margin, margin)) + setUpRecyclerViews() requireViewById<TextView>(R.id.title).text = app?.let { it } ?: resources.getText(R.string.controls_favorite_default_title) requireViewById<TextView>(R.id.subtitle).text = resources.getText(R.string.controls_favorite_subtitle) - currentUserTracker.startTracking() - } + requireViewById<Button>(R.id.done).setOnClickListener { + if (component == null) return@setOnClickListener + val favoritesForStorage = currentModel?.favorites?.map { + with(it.controlStatus.control) { + ControlInfo(component!!, controlId, title, deviceType) + } + } + if (favoritesForStorage != null) { + controller.replaceFavoritesForComponent(component!!, favoritesForStorage) + finishAffinity() + } + } - override fun onResume() { - super.onResume() component?.let { - controller.loadForComponent(it) { + controller.loadForComponent(it) { allControls, favoriteKeys -> executor.execute { - adapter.setItems(it) + val favoriteModel = FavoriteModel( + allControls, + favoriteKeys, + allAdapter = adapterAll, + favoritesAdapter = adapterFavorites) + adapterAll.changeFavoritesModel(favoriteModel) + adapterFavorites.changeFavoritesModel(favoriteModel) + currentModel = favoriteModel } } } + + currentUserTracker.startTracking() + } + + private fun setUpRecyclerViews() { + val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin) + val itemDecorator = MarginItemDecorator(margin, margin) + val layoutInflater = LayoutInflater.from(applicationContext) + + adapterAll = ControlAdapter(layoutInflater) + recyclerViewAll = requireViewById<RecyclerView>(R.id.listAll).apply { + adapter = adapterAll + layoutManager = GridLayoutManager(applicationContext, 2).apply { + spanSizeLookup = adapterAll.spanSizeLookup + } + addItemDecoration(itemDecorator) + } + + adapterFavorites = ControlAdapter(layoutInflater, true) + recyclerViewFavorites = requireViewById<RecyclerView>(R.id.listFavorites).apply { + layoutManager = GridLayoutManager(applicationContext, 2) + adapter = adapterFavorites + addItemDecoration(itemDecorator) + } + ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerViewFavorites) + } + + override fun onDestroy() { + currentUserTracker.stopTracking() + super.onDestroy() } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 645e929d6a10..ad4bdefdff3e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -20,6 +20,7 @@ import android.content.ComponentName import android.content.Intent import android.os.Bundle import android.view.LayoutInflater +import android.view.ViewStub import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -63,11 +64,21 @@ class ControlsProviderSelectorActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.controls_management) + requireViewById<ViewStub>(R.id.stub).apply { + layoutResource = R.layout.controls_management_apps + inflate() + } recyclerView = requireViewById(R.id.list) - recyclerView.adapter = AppAdapter(executor, lifecycle, listingController, - LayoutInflater.from(this), ::launchFavoritingActivity, - FavoritesRenderer(resources, controlsController::countFavoritesForComponent)) + recyclerView.adapter = AppAdapter( + backExecutor, + executor, + lifecycle, + listingController, + LayoutInflater.from(this), + ::launchFavoritingActivity, + FavoritesRenderer(resources, controlsController::countFavoritesForComponent), + resources) recyclerView.layoutManager = LinearLayoutManager(applicationContext) requireViewById<TextView>(R.id.title).text = @@ -89,11 +100,16 @@ class ControlsProviderSelectorActivity @Inject constructor( .apply { putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it)) - putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP + putExtra(Intent.EXTRA_COMPONENT_NAME, it) + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP } startActivity(intent) } } } + + override fun onDestroy() { + currentUserTracker.stopTracking() + super.onDestroy() + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt new file mode 100644 index 000000000000..6bade0aeb998 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/FavoriteModel.kt @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.text.TextUtils +import android.util.Log +import com.android.systemui.controls.ControlStatus +import java.util.Collections +import java.util.Comparator + +/** + * Model for keeping track of current favorites and their order. + * + * This model is to be used with two [ControlAdapter] one that shows only favorites in the current + * order and another that shows all controls, separated by zone. When the favorite state of any + * control is modified or when the favorites are reordered, the adapters are notified of the change. + * + * @param listControls list of all the [ControlStatus] to display. This includes controls currently + * marked as favorites as well as those that have been removed (not returned + * from load) + * @param listFavoritesIds list of the [Control.controlId] for all the favorites, including those + * that have been removed. + * @param favoritesAdapter [ControlAdapter] used by the [RecyclerView] that shows only favorites + * @param allAdapter [ControlAdapter] used by the [RecyclerView] that shows all controls + */ +class FavoriteModel( + private val listControls: List<ControlStatus>, + listFavoritesIds: List<String>, + private val favoritesAdapter: ControlAdapter, + private val allAdapter: ControlAdapter +) { + + companion object { + private const val TAG = "FavoriteModel" + } + + /** + * List of favorite controls ([ControlWrapper]) in order. + * + * Initially, this list will give a list of wrappers in the order specified by the constructor + * variable `listFavoriteIds`. + * + * As the favorites are added, removed or moved, this list will keep track of those changes. + */ + val favorites: List<ControlWrapper> = listFavoritesIds.map { id -> + ControlWrapper(listControls.first { it.control.controlId == id }) + }.toMutableList() + + /** + * List of all controls by zones. + * + * Lists all the controls with the zone names interleaved as a flat list. After each zone name, + * the controls in that zone are listed. Zones are listed in alphabetical order + */ + val all: List<ElementWrapper> = listControls.groupBy { it.control.zone } + .mapKeys { it.key ?: "" } // map null to empty + .toSortedMap(CharSequenceComparator()) + .flatMap { + val controls = it.value.map { ControlWrapper(it) } + if (!TextUtils.isEmpty(it.key)) { + listOf(ZoneNameWrapper(it.key)) + controls + } else { + controls + } + } + + /** + * Change the favorite status of a [Control]. + * + * This can be invoked from any of the [ControlAdapter]. It will change the status of that + * control and either add it to the list of favorites (at the end) or remove it from it. + * + * Removing the favorite status from a Removed control will make it disappear completely if + * changes are saved. + * + * @param controlId the id of the [Control] to change the status + * @param favorite `true` if and only if it's set to be a favorite. + */ + fun changeFavoriteStatus(controlId: String, favorite: Boolean) { + favorites as MutableList + val index = all.indexOfFirst { + it is ControlWrapper && it.controlStatus.control.controlId == controlId + } + val control = (all[index] as ControlWrapper).controlStatus + if (control.favorite == favorite) { + Log.d(TAG, "Changing favorite to same state for ${control.control.controlId} ") + return + } else { + control.favorite = favorite + } + allAdapter.notifyItemChanged(index) + if (favorite) { + favorites.add(all[index] as ControlWrapper) + favoritesAdapter.notifyItemInserted(favorites.size - 1) + } else { + val i = favorites.indexOfFirst { it.controlStatus.control.controlId == controlId } + favorites.removeAt(i) + favoritesAdapter.notifyItemRemoved(i) + } + } + + /** + * Move items in the model and notify the [favoritesAdapter]. + */ + fun onMoveItem(from: Int, to: Int) { + if (from < to) { + for (i in from until to) { + Collections.swap(favorites, i, i + 1) + } + } else { + for (i in from downTo to + 1) { + Collections.swap(favorites, i, i - 1) + } + } + favoritesAdapter.notifyItemMoved(from, to) + } +} + +/** + * Compares [CharSequence] as [String]. + * + * It will have empty strings as the first element + */ +class CharSequenceComparator : Comparator<CharSequence> { + override fun compare(p0: CharSequence?, p1: CharSequence?): Int { + if (p0 == null && p1 == null) return 0 + else if (p0 == null && p1 != null) return -1 + else if (p0 != null && p1 == null) return 1 + return p0.toString().compareTo(p1.toString()) + } +} + +/** + * Wrapper classes for the different types of elements shown in the [RecyclerView]s in + * [ControlsFavoritingActivity]. + */ +sealed class ElementWrapper +data class ZoneNameWrapper(val zoneName: CharSequence) : ElementWrapper() +data class ControlWrapper(val controlStatus: ControlStatus) : ElementWrapper()
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 4e887262659e..ca2226f29528 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -167,6 +167,10 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi // again, it will only show after the brightness sensor has stabilized, // avoiding a potential flicker. scrimOpacity = 255; + } else if (!mScreenOff && mLightSensor == null) { + // No light sensor but previous state turned the screen black. Make the scrim + // transparent and below views visible. + scrimOpacity = 0; } else if (brightnessReady) { // Only unblank scrim once brightness is ready. scrimOpacity = computeScrimOpacity(sensorValue); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index b3fc027d1ac7..a3cd5fdd771b 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -1045,7 +1045,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, Action action = getItem(position); View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); view.setOnClickListener(v -> onClickItem(position)); - view.setOnLongClickListener(v -> onLongClickItem(position)); + if (action instanceof LongPressAction) { + view.setOnLongClickListener(v -> onLongClickItem(position)); + } return view; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 6f03f18ef64b..41b31306a931 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -345,6 +345,14 @@ public class PipBoundsHandler { } /** + * Sets the current bound with the currently store aspect ratio. + * @param stackBounds + */ + public void transformBoundsToAspectRatio(Rect stackBounds) { + transformBoundsToAspectRatio(stackBounds, mAspectRatio, true); + } + + /** * Set the current bounds (or the default bounds if there are no current bounds) with the * specified aspect ratio. */ diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index f9b18cf17abe..c7bfc06829b3 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -280,7 +280,9 @@ public class PipMenuActivityController { if (mToActivityMessenger != null) { Bundle data = new Bundle(); data.putInt(EXTRA_MENU_STATE, menuState); - data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); + if (stackBounds != null) { + data.putParcelable(EXTRA_STACK_BOUNDS, stackBounds); + } data.putParcelable(EXTRA_MOVEMENT_BOUNDS, movementBounds); data.putBoolean(EXTRA_ALLOW_TIMEOUT, allowMenuTimeout); data.putBoolean(EXTRA_WILL_RESIZE_MENU, willResizeMenu); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java new file mode 100644 index 000000000000..9fb623471bf7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.pip.phone; + +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_USER_RESIZE; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT; +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.Region; +import android.hardware.input.InputManager; +import android.os.Looper; +import android.provider.DeviceConfig; +import android.util.DisplayMetrics; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.InputMonitor; +import android.view.MotionEvent; + +import com.android.internal.policy.TaskResizingAlgorithm; +import com.android.systemui.R; +import com.android.systemui.pip.PipBoundsHandler; + +import java.util.concurrent.Executor; + +/** + * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to + * trigger dynamic resize. + */ +public class PipResizeGestureHandler { + + private static final String TAG = "PipResizeGestureHandler"; + + private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + private final PipBoundsHandler mPipBoundsHandler; + private final PipTouchHandler mPipTouchHandler; + private final PipMotionHelper mMotionHelper; + private final int mDisplayId; + private final Executor mMainExecutor; + private final Region mTmpRegion = new Region(); + + private final PointF mDownPoint = new PointF(); + private final Point mMaxSize = new Point(); + private final Point mMinSize = new Point(); + private final Rect mTmpBounds = new Rect(); + private final int mDelta; + + private boolean mAllowGesture = false; + private boolean mIsAttached; + private boolean mIsEnabled; + private boolean mEnablePipResize; + + private InputMonitor mInputMonitor; + private InputEventReceiver mInputEventReceiver; + + private int mCtrlType; + + public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler, + PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper) { + final Resources res = context.getResources(); + context.getDisplay().getMetrics(mDisplayMetrics); + mDisplayId = context.getDisplayId(); + mMainExecutor = context.getMainExecutor(); + mPipBoundsHandler = pipBoundsHandler; + mPipTouchHandler = pipTouchHandler; + mMotionHelper = motionHelper; + + context.getDisplay().getRealSize(mMaxSize); + mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size); + + mEnablePipResize = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + PIP_USER_RESIZE, + /* defaultValue = */ false); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor, + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(PIP_USER_RESIZE)) { + mEnablePipResize = properties.getBoolean( + PIP_USER_RESIZE, /* defaultValue = */ false); + } + } + }); + } + + private void disposeInputChannel() { + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + } + if (mInputMonitor != null) { + mInputMonitor.dispose(); + mInputMonitor = null; + } + } + + void onActivityPinned() { + mIsAttached = true; + updateIsEnabled(); + } + + void onActivityUnpinned() { + mIsAttached = false; + updateIsEnabled(); + } + + private void updateIsEnabled() { + boolean isEnabled = mIsAttached && mEnablePipResize; + if (isEnabled == mIsEnabled) { + return; + } + mIsEnabled = isEnabled; + disposeInputChannel(); + + if (mIsEnabled) { + // Register input event receiver + mInputMonitor = InputManager.getInstance().monitorGestureInput( + "pip-resize", mDisplayId); + mInputEventReceiver = new SysUiInputEventReceiver( + mInputMonitor.getInputChannel(), Looper.getMainLooper()); + } + } + + private void onInputEvent(InputEvent ev) { + if (ev instanceof MotionEvent) { + onMotionEvent((MotionEvent) ev); + } + } + + private boolean isWithinTouchRegion(int x, int y) { + final Rect currentPipBounds = mMotionHelper.getBounds(); + if (currentPipBounds == null) { + return false; + } + + mTmpBounds.set(currentPipBounds); + mTmpBounds.inset(-mDelta, -mDelta); + + mTmpRegion.set(mTmpBounds); + mTmpRegion.op(currentPipBounds, Region.Op.DIFFERENCE); + + if (mTmpRegion.contains(x, y)) { + if (x < currentPipBounds.left) { + mCtrlType |= CTRL_LEFT; + } + if (x > currentPipBounds.right) { + mCtrlType |= CTRL_RIGHT; + } + if (y < currentPipBounds.top) { + mCtrlType |= CTRL_TOP; + } + if (y > currentPipBounds.bottom) { + mCtrlType |= CTRL_BOTTOM; + } + return true; + } + return false; + } + + private void onMotionEvent(MotionEvent ev) { + int action = ev.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + mAllowGesture = isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); + if (mAllowGesture) { + mDownPoint.set(ev.getX(), ev.getY()); + } + + } else if (mAllowGesture) { + final Rect currentPipBounds = mMotionHelper.getBounds(); + Rect newSize = TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(), mDownPoint.x, + mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x, mMinSize.y, mMaxSize, + true, true); + mPipBoundsHandler.transformBoundsToAspectRatio(newSize); + switch (action) { + case MotionEvent.ACTION_POINTER_DOWN: + // We do not support multi touch for resizing via drag + mAllowGesture = false; + break; + case MotionEvent.ACTION_MOVE: + // Capture inputs + mInputMonitor.pilferPointers(); + //TODO: Actually do resize here. + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + //TODO: Finish resize operation here. + mMotionHelper.synchronizePinnedStackBounds(); + mCtrlType = CTRL_NONE; + mAllowGesture = false; + break; + } + } + } + + void updateMaxSize(int maxX, int maxY) { + mMaxSize.set(maxX, maxY); + } + + void updateMiniSize(int minX, int minY) { + mMinSize.set(minX, minY); + } + + class SysUiInputEventReceiver extends InputEventReceiver { + SysUiInputEventReceiver(InputChannel channel, Looper looper) { + super(channel, looper); + } + + public void onInputEvent(InputEvent event) { + PipResizeGestureHandler.this.onInputEvent(event); + finishInputEvent(event, true); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 65cc666d5164..924edb6fe312 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -73,6 +73,7 @@ public class PipTouchHandler { private final ViewConfiguration mViewConfig; private final PipMenuListener mMenuListener = new PipMenuListener(); private final PipBoundsHandler mPipBoundsHandler; + private final PipResizeGestureHandler mPipResizeGestureHandler; private IPinnedStackController mPinnedStackController; private final PipMenuActivityController mMenuController; @@ -188,6 +189,8 @@ public class PipTouchHandler { mGesture = new DefaultPipTouchGesture(); mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mActivityTaskManager, mMenuController, mSnapAlgorithm, mFlingAnimationUtils); + mPipResizeGestureHandler = + new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper); mTouchState = new PipTouchState(mViewConfig, mHandler, () -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(), mMovementBounds, true /* allowMenuTimeout */, willResizeMenu())); @@ -227,6 +230,7 @@ public class PipTouchHandler { public void onActivityPinned() { cleanUp(); mShowPipMenuOnAnimationEnd = true; + mPipResizeGestureHandler.onActivityPinned(); } public void onActivityUnpinned(ComponentName topPipActivity) { @@ -234,11 +238,14 @@ public class PipTouchHandler { // Clean up state after the last PiP activity is removed cleanUp(); } + mPipResizeGestureHandler.onActivityUnpinned(); } public void onPinnedStackAnimationEnded() { // Always synchronize the motion helper bounds once PiP animations finish mMotionHelper.synchronizePinnedStackBounds(); + mPipResizeGestureHandler.updateMiniSize(mMotionHelper.getBounds().width(), + mMotionHelper.getBounds().height()); if (mShowPipMenuOnAnimationEnd) { mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(), @@ -279,6 +286,7 @@ public class PipTouchHandler { Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y); mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight()); + mPipResizeGestureHandler.updateMaxSize(expandedSize.getWidth(), expandedSize.getHeight()); Rect expandedMovementBounds = new Rect(); mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds, bottomOffset); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index ae6162219afa..c118630fe91a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -191,6 +191,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener mTile.setLabel(tile.getLabel()); mTile.setSubtitle(tile.getSubtitle()); mTile.setContentDescription(tile.getContentDescription()); + mTile.setStateDescription(tile.getStateDescription()); mTile.setState(tile.getState()); } @@ -345,6 +346,12 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener state.contentDescription = state.label; } + if (mTile.getStateDescription() != null) { + state.stateDescription = mTile.getStateDescription(); + } else { + state.stateDescription = null; + } + if (state instanceof BooleanState) { state.expandedAccessibilityClassName = Switch.class.getName(); ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 2fe64d26f3ac..8feee10c7e83 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -63,7 +63,6 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private String mAccessibilityClass; private boolean mTileState; private boolean mCollapsedView; - private boolean mClicked; private boolean mShowRippleEffect = true; private final ImageView mBg; @@ -230,13 +229,35 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setLongClickable(state.handlesLongClick); mIcon.setIcon(state, allowAnimations); setContentDescription(state.contentDescription); + final StringBuilder stateDescription = new StringBuilder(); + switch (state.state) { + case Tile.STATE_UNAVAILABLE: + stateDescription.append(mContext.getString(R.string.tile_unavailable)); + break; + case Tile.STATE_INACTIVE: + if (state instanceof QSTile.BooleanState) { + stateDescription.append(mContext.getString(R.string.switch_bar_off)); + } + break; + case Tile.STATE_ACTIVE: + if (state instanceof QSTile.BooleanState) { + stateDescription.append(mContext.getString(R.string.switch_bar_on)); + } + break; + default: + break; + } + if (!TextUtils.isEmpty(state.stateDescription)) { + stateDescription.append(", "); + stateDescription.append(state.stateDescription); + } + setStateDescription(stateDescription.toString()); mAccessibilityClass = state.state == Tile.STATE_UNAVAILABLE ? null : state.expandedAccessibilityClassName; if (state instanceof QSTile.BooleanState) { boolean newState = ((BooleanState) state).value; if (mTileState != newState) { - mClicked = false; mTileState = newState; } } @@ -288,23 +309,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } @Override - public boolean performClick() { - mClicked = true; - return super.performClick(); - } - - @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); if (!TextUtils.isEmpty(mAccessibilityClass)) { event.setClassName(mAccessibilityClass); - if (Switch.class.getName().equals(mAccessibilityClass)) { - boolean b = mClicked ? !mTileState : mTileState; - String label = getResources() - .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off); - event.setContentDescription(label); - event.setChecked(b); - } } } @@ -316,11 +324,13 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { if (!TextUtils.isEmpty(mAccessibilityClass)) { info.setClassName(mAccessibilityClass); if (Switch.class.getName().equals(mAccessibilityClass)) { - boolean b = mClicked ? !mTileState : mTileState; - String label = getResources() - .getString(b ? R.string.switch_bar_on : R.string.switch_bar_off); + String label = getResources().getString( + mTileState ? R.string.switch_bar_on : R.string.switch_bar_off); + // Set the text here for tests in + // android.platform.test.scenario.sysui.quicksettings. Can be removed when + // UiObject2 has a new getStateDescription() API and tests are updated. info.setText(label); - info.setChecked(b); + info.setChecked(mTileState); info.setCheckable(true); if (isLongClickable()) { info.addAction( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 9282a2e3b312..361b6c1b1260 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -134,25 +134,27 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { state.label = mContext.getString(R.string.quick_settings_bluetooth_label); state.secondaryLabel = TextUtils.emptyIfNull( getSecondaryLabel(enabled, connecting, connected, state.isTransient)); + state.contentDescription = state.label; + state.stateDescription = ""; if (enabled) { if (connected) { state.icon = new BluetoothConnectedTileIcon(); if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) { state.label = mController.getConnectedDeviceName(); } - state.contentDescription = + state.stateDescription = mContext.getString(R.string.accessibility_bluetooth_name, state.label) + ", " + state.secondaryLabel; } else if (state.isTransient) { state.icon = ResourceIcon.get( com.android.internal.R.drawable.ic_bluetooth_transient_animation); - state.contentDescription = state.secondaryLabel; + state.stateDescription = state.secondaryLabel; } else { state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth); state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_bluetooth) + "," - + mContext.getString(R.string.accessibility_not_connected); + R.string.accessibility_quick_settings_bluetooth); + state.stateDescription = mContext.getString(R.string.accessibility_not_connected); } state.state = Tile.STATE_ACTIVE; } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 32b051e35604..58de0575fa75 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -183,6 +183,7 @@ public class CastTile extends QSTileImpl<BooleanState> { protected void handleUpdateState(BooleanState state, Object arg) { state.label = mContext.getString(R.string.quick_settings_cast_title); state.contentDescription = state.label; + state.stateDescription = ""; state.value = false; final List<CastDevice> devices = mController.getCastDevices(); boolean connecting = false; @@ -192,8 +193,9 @@ public class CastTile extends QSTileImpl<BooleanState> { if (device.state == CastDevice.STATE_CONNECTED) { state.value = true; state.secondaryLabel = getDeviceName(device); - state.contentDescription = state.contentDescription + "," - + mContext.getString(R.string.accessibility_cast_name, state.label); + state.stateDescription = state.stateDescription + "," + + mContext.getString( + R.string.accessibility_cast_name, state.label); connecting = false; break; } else if (device.state == CastDevice.STATE_CONNECTING) { @@ -217,9 +219,8 @@ public class CastTile extends QSTileImpl<BooleanState> { state.state = Tile.STATE_UNAVAILABLE; String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi); state.secondaryLabel = noWifi; - state.contentDescription = state.contentDescription + ", " + mContext.getString( - R.string.accessibility_quick_settings_not_available, noWifi); } + state.stateDescription = state.stateDescription + ", " + state.secondaryLabel; mDetailAdapter.updateItems(devices); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java index 22470c7f5af5..d5f86c951407 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java @@ -194,17 +194,13 @@ public class CellularTile extends QSTileImpl<SignalState> { state.secondaryLabel = r.getString(R.string.cell_data_off); } - - // TODO(b/77881974): Instead of switching out the description via a string check for - // we need to have two strings provided by the MobileIconGroup. - final CharSequence contentDescriptionSuffix; + state.contentDescription = state.label; if (state.state == Tile.STATE_INACTIVE) { - contentDescriptionSuffix = r.getString(R.string.cell_data_off_content_description); + // This information is appended later by converting the Tile.STATE_INACTIVE state. + state.stateDescription = ""; } else { - contentDescriptionSuffix = state.secondaryLabel; + state.stateDescription = state.secondaryLabel; } - - state.contentDescription = state.label + ", " + contentDescriptionSuffix; } private CharSequence appendMobileDataType(CharSequence current, CharSequence dataType) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 52d1a5b3b991..9215da4cda9a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -240,6 +240,8 @@ public class DndTile extends QSTileImpl<BooleanState> { zen != Global.ZEN_MODE_OFF, mController.getConfig(), false)); state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_dnd); checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME); + // Keeping the secondaryLabel in contentDescription instead of stateDescription is easier + // to understand. switch (zen) { case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: state.contentDescription = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index dafdd89ee62c..792c36477962 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -102,14 +102,13 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements } state.label = mHost.getContext().getString(R.string.quick_settings_flashlight_label); state.secondaryLabel = ""; + state.stateDescription = ""; if (!mFlashlightController.isAvailable()) { state.icon = mIcon; state.slash.isSlashed = true; state.secondaryLabel = mContext.getString( R.string.quick_settings_flashlight_camera_in_use); - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_flashlight_unavailable) - + ", " + state.secondaryLabel; + state.stateDescription = state.secondaryLabel; state.state = Tile.STATE_UNAVAILABLE; return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 42f80109e045..91b3ae480af9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -148,6 +148,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> { state.secondaryLabel = getSecondaryLabel( isTileActive, isTransient, isDataSaverEnabled, numConnectedDevices); + state.stateDescription = state.secondaryLabel; } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index fbdca3ba1c7b..e617867eb10e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -105,15 +105,8 @@ public class LocationTile extends QSTileImpl<BooleanState> { } state.icon = mIcon; state.slash.isSlashed = !state.value; - if (locationEnabled) { - state.label = mContext.getString(R.string.quick_settings_location_label); - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_location_on); - } else { - state.label = mContext.getString(R.string.quick_settings_location_label); - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_location_off); - } + state.label = mContext.getString(R.string.quick_settings_location_label); + state.contentDescription = state.label; state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.expandedAccessibilityClassName = Switch.class.getName(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index b7ce101cacab..6e8dcf36bacc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -195,6 +195,7 @@ public class WifiTile extends QSTileImpl<SignalState> { state.activityIn = cb.enabled && cb.activityIn; state.activityOut = cb.enabled && cb.activityOut; final StringBuffer minimalContentDescription = new StringBuffer(); + final StringBuffer minimalStateDescription = new StringBuffer(); final Resources r = mContext.getResources(); if (isTransient) { state.icon = ResourceIcon.get( @@ -219,13 +220,14 @@ public class WifiTile extends QSTileImpl<SignalState> { mContext.getString(R.string.quick_settings_wifi_label)).append(","); if (state.value) { if (wifiConnected) { - minimalContentDescription.append(cb.wifiSignalContentDescription).append(","); + minimalStateDescription.append(cb.wifiSignalContentDescription); minimalContentDescription.append(removeDoubleQuotes(cb.ssid)); if (!TextUtils.isEmpty(state.secondaryLabel)) { minimalContentDescription.append(",").append(state.secondaryLabel); } } } + state.stateDescription = minimalStateDescription.toString(); state.contentDescription = minimalContentDescription.toString(); state.dualLabelContentDescription = r.getString( R.string.accessibility_quick_settings_open_settings, getTileLabel()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index 7853dc388bcb..e54ee51fb9d4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -103,14 +103,11 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements state.icon = mIcon; if (state.value) { state.slash.isSlashed = false; - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_work_mode_on); } else { state.slash.isSlashed = true; - state.contentDescription = mContext.getString( - R.string.accessibility_quick_settings_work_mode_off); } state.label = mContext.getString(R.string.quick_settings_work_mode_label); + state.contentDescription = state.label; state.expandedAccessibilityClassName = Switch.class.getName(); state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 880b8f8776e8..4f38a15824a4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -69,6 +69,7 @@ import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.animation.Interpolator; +import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; @@ -143,7 +144,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private static final float BACKGROUND_ALPHA = 0.5f; private static final float SCREENSHOT_DROP_IN_MIN_SCALE = 0.725f; private static final float ROUNDED_CORNER_RADIUS = .05f; - private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000; + private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000; private static final int MESSAGE_CORNER_TIMEOUT = 2; private final ScreenshotNotificationsController mNotificationsController; @@ -162,6 +163,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private final HorizontalScrollView mActionsContainer; private final LinearLayout mActionsView; private final ImageView mBackgroundProtection; + private final FrameLayout mDismissButton; private Bitmap mScreenBitmap; private AnimatorSet mScreenshotAnimation; @@ -170,6 +172,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private float mScreenshotOffsetYPx; private float mScreenshotHeightPx; private float mCornerScale; + private float mDismissButtonSize; private AsyncTask<Void, Void, Void> mSaveInBgTask; @@ -216,19 +219,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions); mBackgroundProtection = mScreenshotLayout.findViewById( R.id.global_screenshot_actions_background); + mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button); + mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button")); mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash); mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector); mScreenshotLayout.setFocusable(true); mScreenshotSelectorView.setFocusable(true); mScreenshotSelectorView.setFocusableInTouchMode(true); - mScreenshotLayout.setOnTouchListener((v, event) -> { - if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { - clearScreenshot("tap_outside"); - } - // Intercept and ignore all touch events - return true; - }); // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams( @@ -254,6 +252,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y); mCornerScale = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale) / (float) mDisplayMetrics.widthPixels; + mDismissButtonSize = resources.getDimensionPixelSize( + R.dimen.screenshot_dismiss_button_tappable_size); // Setup the Camera shutter sound mCameraSound = new MediaActionSound(); @@ -271,6 +271,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset Rect actionsRect = new Rect(); mActionsContainer.getBoundsOnScreen(actionsRect); touchRegion.op(actionsRect, Region.Op.UNION); + Rect dismissRect = new Rect(); + mDismissButton.getBoundsOnScreen(dismissRect); + touchRegion.op(dismissRect, Region.Op.UNION); inoutInfo.touchableRegion.set(touchRegion); } @@ -408,6 +411,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mActionsContainer.setVisibility(View.GONE); mBackgroundView.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); + mDismissButton.setVisibility(View.GONE); mScreenshotView.setVisibility(View.GONE); mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); } @@ -615,6 +619,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotView.setTranslationX(t * finalPos.x); mScreenshotView.setTranslationY(t * finalPos.y); }); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + Rect bounds = new Rect(); + mScreenshotView.getBoundsOnScreen(bounds); + mDismissButton.setX(bounds.right - mDismissButtonSize / 2f); + mDismissButton.setY(bounds.top - mDismissButtonSize / 2f); + mDismissButton.setVisibility(View.VISIBLE); + } + }); return anim; } @@ -686,14 +701,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mBackgroundProtection.setAlpha(t); mActionsContainer.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t); }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - mScreenshotView.requestFocus(); - mScreenshotView.setElevation(50); - } - }); return animator; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index bb0681ce8a44..a0af4ace05b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -23,14 +23,6 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_DEMOTE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE; -import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_UNBUBBLE; - import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; @@ -69,11 +61,13 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.phone.ShadeController; import java.lang.annotation.Retention; import java.util.Arrays; @@ -92,6 +86,7 @@ public class NotificationConversationInfo extends LinearLayout implements ShortcutManager mShortcutManager; private PackageManager mPm; private VisualStabilityManager mVisualStabilityManager; + private ShadeController mShadeController; private String mPackageName; private String mAppName; @@ -103,24 +98,30 @@ public class NotificationConversationInfo extends LinearLayout implements private NotificationEntry mEntry; private StatusBarNotification mSbn; private boolean mIsDeviceProvisioned; - private int mStartingChannelImportance; private boolean mStartedAsBubble; private boolean mIsBubbleable; - // TODO: remove when launcher api works - @VisibleForTesting - boolean mShowHomeScreen = false; - private @UpdateChannelRunnable.Action int mSelectedAction = -1; + private @Action int mSelectedAction = -1; private OnSnoozeClickListener mOnSnoozeClickListener; private OnSettingsClickListener mOnSettingsClickListener; - private OnAppSettingsClickListener mAppSettingsClickListener; private NotificationGuts mGutsContainer; private BubbleController mBubbleController; @VisibleForTesting boolean mSkipPost = false; + @Retention(SOURCE) + @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE}) + private @interface Action {} + static final int ACTION_BUBBLE = 0; + static final int ACTION_HOME = 1; + static final int ACTION_FAVORITE = 2; + static final int ACTION_SNOOZE = 3; + static final int ACTION_MUTE = 4; + static final int ACTION_SETTINGS = 5; + static final int ACTION_UNBUBBLE = 6; + private OnClickListener mOnBubbleClick = v -> { mSelectedAction = mStartedAsBubble ? ACTION_UNBUBBLE : ACTION_BUBBLE; if (mStartedAsBubble) { @@ -136,12 +137,14 @@ public class NotificationConversationInfo extends LinearLayout implements private OnClickListener mOnHomeClick = v -> { mSelectedAction = ACTION_HOME; mShortcutManager.requestPinShortcut(mShortcutInfo, null); + mShadeController.animateCollapsePanels(); closeControls(v, true); }; private OnClickListener mOnFavoriteClick = v -> { mSelectedAction = ACTION_FAVORITE; - closeControls(v, true); + updateChannel(); + }; private OnClickListener mOnSnoozeClick = v -> { @@ -151,13 +154,8 @@ public class NotificationConversationInfo extends LinearLayout implements }; private OnClickListener mOnMuteClick = v -> { - mSelectedAction = ACTION_MUTE; - closeControls(v, true); - }; - - private OnClickListener mOnDemoteClick = v -> { - mSelectedAction = ACTION_DEMOTE; - closeControls(v, true); + mSelectedAction = ACTION_MUTE; + updateChannel(); }; public NotificationConversationInfo(Context context, AttributeSet attrs) { @@ -197,15 +195,14 @@ public class NotificationConversationInfo extends LinearLayout implements mEntry = entry; mSbn = entry.getSbn(); mPm = pm; - mAppSettingsClickListener = onAppSettingsClick; mAppName = mPackageName; mOnSettingsClickListener = onSettingsClick; mNotificationChannel = notificationChannel; - mStartingChannelImportance = mNotificationChannel.getImportance(); mAppUid = mSbn.getUid(); mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; mOnSnoozeClickListener = onSnoozeClickListener; + mShadeController = Dependency.get(ShadeController.class); mShortcutManager = shortcutManager; mLauncherApps = launcherApps; @@ -251,9 +248,6 @@ public class NotificationConversationInfo extends LinearLayout implements mNotificationChannel = mINotificationManager.getConversationNotificationChannel( mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName, mNotificationChannel.getId(), false, mConversationId); - - // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a - // time } catch (RemoteException e) { Slog.e(TAG, "Could not create conversation channel", e); } @@ -274,40 +268,24 @@ public class NotificationConversationInfo extends LinearLayout implements Button home = findViewById(R.id.home); home.setOnClickListener(mOnHomeClick); - home.setVisibility(mShowHomeScreen && mShortcutInfo != null + home.setVisibility(mShortcutInfo != null && mShortcutManager.isRequestPinShortcutSupported() ? VISIBLE : GONE); - Button favorite = findViewById(R.id.fave); + View favorite = findViewById(R.id.fave); favorite.setOnClickListener(mOnFavoriteClick); - if (mNotificationChannel.isImportantConversation()) { - favorite.setText(R.string.notification_conversation_unfavorite); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_star), null, null, null); - } else { - favorite.setText(R.string.notification_conversation_favorite); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_star_border), null, null, null); - } Button snooze = findViewById(R.id.snooze); snooze.setOnClickListener(mOnSnoozeClick); - Button mute = findViewById(R.id.mute); + View mute = findViewById(R.id.mute); mute.setOnClickListener(mOnMuteClick); - if (mStartingChannelImportance >= IMPORTANCE_DEFAULT - || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) { - mute.setText(R.string.notification_conversation_mute); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null); - } else { - mute.setText(R.string.notification_conversation_unmute); - favorite.setCompoundDrawablesRelative( - mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null); - } - ImageButton demote = findViewById(R.id.demote); - demote.setOnClickListener(mOnDemoteClick); + final View settingsButton = findViewById(R.id.info); + settingsButton.setOnClickListener(getSettingsOnClickListener()); + settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); + + updateToggleActions(); } private void bindHeader() { @@ -315,26 +293,6 @@ public class NotificationConversationInfo extends LinearLayout implements // Delegate bindDelegate(); - - // Set up app settings link (i.e. Customize) - View settingsLinkView = findViewById(R.id.app_settings); - Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, - mNotificationChannel, - mSbn.getId(), mSbn.getTag()); - if (settingsIntent != null - && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { - settingsLinkView.setVisibility(VISIBLE); - settingsLinkView.setOnClickListener((View view) -> { - mAppSettingsClickListener.onClick(view, settingsIntent); - }); - } else { - settingsLinkView.setVisibility(View.GONE); - } - - // System Settings button. - final View settingsButton = findViewById(R.id.info); - settingsButton.setOnClickListener(getSettingsOnClickListener()); - settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); } private OnClickListener getSettingsOnClickListener() { @@ -424,15 +382,12 @@ public class NotificationConversationInfo extends LinearLayout implements private void bindDelegate() { TextView delegateView = findViewById(R.id.delegate_name); - TextView dividerView = findViewById(R.id.pkg_divider); if (!TextUtils.equals(mPackageName, mDelegatePkg)) { // this notification was posted by a delegate! delegateView.setVisibility(View.VISIBLE); - dividerView.setVisibility(View.VISIBLE); } else { delegateView.setVisibility(View.GONE); - dividerView.setVisibility(View.GONE); } } @@ -492,26 +447,37 @@ public class NotificationConversationInfo extends LinearLayout implements } } - private Intent getAppSettingsIntent(PackageManager pm, String packageName, - NotificationChannel channel, int id, String tag) { - Intent intent = new Intent(Intent.ACTION_MAIN) - .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) - .setPackage(packageName); - final List<ResolveInfo> resolveInfos = pm.queryIntentActivities( - intent, - PackageManager.MATCH_DEFAULT_ONLY - ); - if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) { - return null; + private void updateToggleActions() { + ImageButton favorite = findViewById(R.id.fave); + if (mNotificationChannel.isImportantConversation()) { + favorite.setContentDescription( + mContext.getString(R.string.notification_conversation_favorite)); + favorite.setImageResource(R.drawable.ic_important); + } else { + favorite.setContentDescription( + mContext.getString(R.string.notification_conversation_unfavorite)); + favorite.setImageResource(R.drawable.ic_important_outline); } - final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; - intent.setClassName(activityInfo.packageName, activityInfo.name); - if (channel != null) { - intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId()); + + ImageButton mute = findViewById(R.id.mute); + if (mNotificationChannel.getImportance() >= IMPORTANCE_DEFAULT + || mNotificationChannel.getImportance() == IMPORTANCE_UNSPECIFIED) { + mute.setContentDescription( + mContext.getString(R.string.notification_conversation_unmute)); + mute.setImageResource(R.drawable.ic_notifications_alert); + } else { + mute.setContentDescription( + mContext.getString(R.string.notification_conversation_mute)); + mute.setImageResource(R.drawable.ic_notifications_silence); } - intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id); - intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag); - return intent; + } + + private void updateChannel() { + Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); + bgHandler.post( + new UpdateChannelRunnable(mINotificationManager, mPackageName, + mAppUid, mSelectedAction, mNotificationChannel)); + mVisualStabilityManager.temporarilyAllowReordering(); } /** @@ -556,11 +522,7 @@ public class NotificationConversationInfo extends LinearLayout implements @Override public boolean handleCloseControls(boolean save, boolean force) { if (save && mSelectedAction > -1) { - Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); - bgHandler.post( - new UpdateChannelRunnable(mINotificationManager, mPackageName, - mAppUid, mSelectedAction, mNotificationChannel)); - mVisualStabilityManager.temporarilyAllowReordering(); + updateChannel(); } return false; } @@ -575,19 +537,7 @@ public class NotificationConversationInfo extends LinearLayout implements return false; } - static class UpdateChannelRunnable implements Runnable { - - @Retention(SOURCE) - @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE, - ACTION_DEMOTE}) - private @interface Action {} - static final int ACTION_BUBBLE = 0; - static final int ACTION_HOME = 1; - static final int ACTION_FAVORITE = 2; - static final int ACTION_SNOOZE = 3; - static final int ACTION_MUTE = 4; - static final int ACTION_DEMOTE = 5; - static final int ACTION_UNBUBBLE = 6; + class UpdateChannelRunnable implements Runnable { private final INotificationManager mINotificationManager; private final String mAppPkg; @@ -633,10 +583,6 @@ public class NotificationConversationInfo extends LinearLayout implements mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT)); } break; - case ACTION_DEMOTE: - mChannelToUpdate.setDemoted(!mChannelToUpdate.isDemoted()); - break; - } if (channelSettingChanged) { @@ -646,13 +592,7 @@ public class NotificationConversationInfo extends LinearLayout implements } catch (RemoteException e) { Log.e(TAG, "Unable to update notification channel", e); } + ThreadUtils.postOnMainThread(() -> updateToggleActions()); } } - - @Retention(SOURCE) - @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE}) - private @interface AlertingBehavior {} - private static final int BEHAVIOR_ALERTING = 0; - private static final int BEHAVIOR_SILENT = 1; - private static final int BEHAVIOR_BUBBLE = 2; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index 6789c814dcee..352abcfc9214 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -384,6 +385,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx guts.resetFalsingCheck(); mOnSettingsClickListener.onSettingsClick(sbn.getKey()); startAppNotificationSettingsActivity(packageName, appUid, channel, row); + notificationInfoView.closeControls(v, false); }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 44a320419309..745843deeddb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -375,6 +375,7 @@ public class EdgeBackGestureHandler implements DisplayListener, mDownPoint.set(ev.getX(), ev.getY()); mThresholdCrossed = false; } + } else if (mAllowGesture) { if (!mThresholdCrossed) { if (action == MotionEvent.ACTION_POINTER_DOWN) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 9e64748f2e65..3f5215e1a639 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -269,6 +269,17 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } }; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = + new DeviceConfig.OnPropertiesChangedListener() { + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) { + mForceNavBarHandleOpaque = properties.getBoolean( + NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true); + } + } + }; + @Inject public NavigationBarFragment(AccessibilityManagerWrapper accessibilityManagerWrapper, DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, @@ -298,21 +309,6 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mDivider = divider; mRecentsOptional = recentsOptional; mHandler = mainHandler; - - mForceNavBarHandleOpaque = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, - NAV_BAR_HANDLE_FORCE_OPAQUE, - /* defaultValue = */ true); - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) { - mForceNavBarHandleOpaque = properties.getBoolean( - NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ true); - } - } - }); } // ----- Fragment Lifecycle Callbacks ----- @@ -338,6 +334,13 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback // Respect the latest disabled-flags. mCommandQueue.recomputeDisableFlags(mDisplayId, false); + + mForceNavBarHandleOpaque = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + NAV_BAR_HANDLE_FORCE_OPAQUE, + /* defaultValue = */ true); + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener); } @Override @@ -346,6 +349,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mNavigationModeController.removeListener(this); mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener); mContentResolver.unregisterContentObserver(mAssistContentObserver); + + DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index e3bcdc8b0b60..751217f03fa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -225,7 +226,7 @@ class ControlsControllerImplTest : SysuiTestCase() { val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) val control = builderFromInfo(newControlInfo).build() - controller.loadForComponent(TEST_COMPONENT) {} + controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit } reset(persistenceWrapper) verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -293,11 +294,13 @@ class ControlsControllerImplTest : SysuiTestCase() { var loaded = false val control = builderFromInfo(TEST_CONTROL_INFO).build() - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(1, it.size) - val controlStatus = it[0] + assertEquals(1, controls.size) + val controlStatus = controls[0] assertEquals(ControlStatus(control, false), controlStatus) + + assertTrue(favorites.isEmpty()) } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -315,14 +318,17 @@ class ControlsControllerImplTest : SysuiTestCase() { val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build() controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(2, it.size) - val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID } + assertEquals(2, controls.size) + val controlStatus = controls.first { it.control.controlId == TEST_CONTROL_ID } assertEquals(ControlStatus(control, true), controlStatus) - val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 } + val controlStatus2 = controls.first { it.control.controlId == TEST_CONTROL_ID_2 } assertEquals(ControlStatus(control2, false), controlStatus2) + + assertEquals(1, favorites.size) + assertEquals(TEST_CONTROL_ID, favorites[0]) } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -338,13 +344,16 @@ class ControlsControllerImplTest : SysuiTestCase() { var loaded = false controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) - controller.loadForComponent(TEST_COMPONENT) { + controller.loadForComponent(TEST_COMPONENT) { controls, favorites -> loaded = true - assertEquals(1, it.size) - val controlStatus = it[0] + assertEquals(1, controls.size) + val controlStatus = controls[0] assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId) assertTrue(controlStatus.favorite) assertTrue(controlStatus.removed) + + assertEquals(1, favorites.size) + assertEquals(TEST_CONTROL_ID, favorites[0]) } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), @@ -361,7 +370,7 @@ class ControlsControllerImplTest : SysuiTestCase() { val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) val control = builderFromInfo(newControlInfo).build() - controller.loadForComponent(TEST_COMPONENT) {} + controller.loadForComponent(TEST_COMPONENT) { _, _ -> Unit } verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) @@ -483,4 +492,81 @@ class ControlsControllerImplTest : SysuiTestCase() { assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT_2)) } + + @Test + fun testGetFavoritesForComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testGetFavoritesForComponent_otherComponent() { + controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true) + assertTrue(controller.getFavoritesForComponent(TEST_COMPONENT).isEmpty()) + } + + @Test + fun testGetFavoritesForComponent_multipleInOrder() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + controller.changeFavoriteStatus(controlInfo, true) + + assertEquals(listOf(TEST_CONTROL_INFO, controlInfo), + controller.getFavoritesForComponent(TEST_COMPONENT)) + + controller.clearFavorites() + + controller.changeFavoriteStatus(controlInfo, true) + controller.changeFavoriteStatus(TEST_CONTROL_INFO, true) + + assertEquals(listOf(controlInfo, TEST_CONTROL_INFO), + controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_noFavorites() { + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_differentComponentsAreFilteredOut() { + controller.replaceFavoritesForComponent(TEST_COMPONENT, + listOf(TEST_CONTROL_INFO, TEST_CONTROL_INFO_2)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_oldFavoritesRemoved() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + assertNotEquals(TEST_CONTROL_INFO, controlInfo) + + controller.changeFavoriteStatus(controlInfo, true) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOf(TEST_CONTROL_INFO)) + + assertEquals(1, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOf(TEST_CONTROL_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) + } + + @Test + fun testReplaceFavoritesForComponent_favoritesInOrder() { + val controlInfo = ControlInfo(TEST_COMPONENT, "id", "title", 0) + + val listOrder1 = listOf(TEST_CONTROL_INFO, controlInfo) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder1) + + assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOrder1, controller.getFavoritesForComponent(TEST_COMPONENT)) + + val listOrder2 = listOf(controlInfo, TEST_CONTROL_INFO) + controller.replaceFavoritesForComponent(TEST_COMPONENT, listOrder2) + + assertEquals(2, controller.countFavoritesForComponent(TEST_COMPONENT)) + assertEquals(listOrder2, controller.getFavoritesForComponent(TEST_COMPONENT)) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt new file mode 100644 index 000000000000..9ffc29e0eb7e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoriteModelTest.kt @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.app.PendingIntent +import android.service.controls.Control +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlStatus +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Assert.fail +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +open class FavoriteModelTest : SysuiTestCase() { + + @Mock + lateinit var pendingIntent: PendingIntent + @Mock + lateinit var allAdapter: ControlAdapter + @Mock + lateinit var favoritesAdapter: ControlAdapter + + val idPrefix = "controlId" + val favoritesIndices = listOf(7, 3, 1, 9) + val favoritesList = favoritesIndices.map { "controlId$it" } + lateinit var controls: List<ControlStatus> + + lateinit var model: FavoriteModel + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + // controlId0 --> zone = 0 + // controlId1 --> zone = 1, favorite + // controlId2 --> zone = 2 + // controlId3 --> zone = 0, favorite + // controlId4 --> zone = 1 + // controlId5 --> zone = 2 + // controlId6 --> zone = 0 + // controlId7 --> zone = 1, favorite + // controlId8 --> zone = 2 + // controlId9 --> zone = 0, favorite + controls = (0..9).map { + ControlStatus( + Control.StatelessBuilder("$idPrefix$it", pendingIntent) + .setZone((it % 3).toString()) + .build(), + it in favoritesIndices + ) + } + + model = FavoriteModel(controls, favoritesList, favoritesAdapter, allAdapter) + } +} + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class FavoriteModelNonParametrizedTests : FavoriteModelTest() { + @Test + fun testAll() { + // Zones are sorted alphabetically + val expected = listOf( + ZoneNameWrapper("0"), + ControlWrapper(controls[0]), + ControlWrapper(controls[3]), + ControlWrapper(controls[6]), + ControlWrapper(controls[9]), + ZoneNameWrapper("1"), + ControlWrapper(controls[1]), + ControlWrapper(controls[4]), + ControlWrapper(controls[7]), + ZoneNameWrapper("2"), + ControlWrapper(controls[2]), + ControlWrapper(controls[5]), + ControlWrapper(controls[8]) + ) + assertEquals(expected, model.all) + } + + @Test + fun testFavoritesInOrder() { + val expected = favoritesIndices.map { ControlWrapper(controls[it]) } + assertEquals(expected, model.favorites) + } + + @Test + fun testChangeFavoriteStatus_addFavorite() { + val controlToAdd = 6 + model.changeFavoriteStatus("$idPrefix$controlToAdd", true) + + val pair = model.all.findControl(controlToAdd) + pair?.let { + assertTrue(it.second.favorite) + assertEquals(it.second, model.favorites.last().controlStatus) + verify(favoritesAdapter).notifyItemInserted(model.favorites.size - 1) + verify(allAdapter).notifyItemChanged(it.first) + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } ?: run { + fail("control not found") + } + } + + @Test + fun testChangeFavoriteStatus_removeFavorite() { + val controlToRemove = 3 + model.changeFavoriteStatus("$idPrefix$controlToRemove", false) + + val pair = model.all.findControl(controlToRemove) + pair?.let { + assertFalse(it.second.favorite) + assertTrue(model.favorites.none { + it.controlStatus.control.controlId == "$idPrefix$controlToRemove" + }) + verify(favoritesAdapter).notifyItemRemoved(favoritesIndices.indexOf(controlToRemove)) + verify(allAdapter).notifyItemChanged(it.first) + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } ?: run { + fail("control not found") + } + } + + @Test + fun testChangeFavoriteStatus_sameStatus() { + model.changeFavoriteStatus("${idPrefix}7", true) + model.changeFavoriteStatus("${idPrefix}6", false) + + val expected = favoritesIndices.map { ControlWrapper(controls[it]) } + assertEquals(expected, model.favorites) + + verifyNoMoreInteractions(favoritesAdapter, allAdapter) + } + + private fun List<ElementWrapper>.findControl(controlIndex: Int): Pair<Int, ControlStatus>? { + val index = indexOfFirst { + it is ControlWrapper && + it.controlStatus.control.controlId == "$idPrefix$controlIndex" + } + return if (index == -1) null else index to (get(index) as ControlWrapper).controlStatus + } +} + +@SmallTest +@RunWith(Parameterized::class) +class FavoriteModelParameterizedTest(val from: Int, val to: Int) : FavoriteModelTest() { + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0} -> {1}") + fun data(): Collection<Array<Int>> { + return (0..3).flatMap { from -> + (0..3).map { to -> + arrayOf(from, to) + } + }.filterNot { it[0] == it[1] } + } + } + + @Test + fun testMoveItem() { + val originalFavorites = model.favorites.toList() + val originalFavoritesIds = + model.favorites.map { it.controlStatus.control.controlId }.toSet() + model.onMoveItem(from, to) + assertEquals(originalFavorites[from], model.favorites[to]) + // Check that we still have the same favorites + assertEquals(originalFavoritesIds, + model.favorites.map { it.controlStatus.control.controlId }.toSet()) + + verify(favoritesAdapter).notifyItemMoved(from, to) + + verifyNoMoreInteractions(allAdapter, favoritesAdapter) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index 399f723f4d62..9117ea8f9fc2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -173,6 +173,21 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { } @Test + public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception { + mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, + null /* sensor */, mBroadcastDispatcher, mDozeHost, null /* handler */, + DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY, + true /* debuggable */); + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + mScreen.transitionTo(INITIALIZED, DOZE); + reset(mDozeHost); + + mScreen.transitionTo(DOZE, DOZE_REQUEST_PULSE); + + verify(mDozeHost).setAodDimmingScrim(eq(0f)); + } + + @Test public void testDockedAod_usesLightSensor() { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD_DOCKED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index f080d67bfffb..e8de10f7392b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -75,6 +75,7 @@ import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.notification.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.phone.ShadeController; import org.junit.Before; import org.junit.Rule; @@ -131,6 +132,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase { private ShortcutManager mShortcutManager; @Mock private NotificationGuts mNotificationGuts; + @Mock + private ShadeController mShadeController; @Before public void setUp() throws Exception { @@ -139,12 +142,12 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger); mDependency.injectTestDependency(BubbleController.class, mBubbleController); + mDependency.injectTestDependency(ShadeController.class, mShadeController); // Inflate the layout final LayoutInflater layoutInflater = LayoutInflater.from(mContext); mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate( R.layout.notification_conversation_info, null); - mNotificationInfo.mShowHomeScreen = true; mNotificationInfo.setGutsParent(mNotificationGuts); doAnswer((Answer<Object>) invocation -> { mNotificationInfo.handleCloseControls(true, false); @@ -173,7 +176,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { when(mShortcutInfo.getShortLabel()).thenReturn("Convo name"); List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo); when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts); - mImage = mContext.getDrawable(R.drawable.ic_star); + mImage = mContext.getDrawable(R.drawable.ic_remove); when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo), anyInt())).thenReturn(mImage); @@ -333,8 +336,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); - final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); - assertEquals(GONE, dividerView.getVisibility()); } @Test @@ -364,8 +365,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(VISIBLE, nameView.getVisibility()); assertTrue(nameView.getText().toString().contains("Proxied")); - final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); - assertEquals(VISIBLE, dividerView.getVisibility()); } @Test @@ -502,6 +501,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase { verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), anyInt(), any()); + verify(mShadeController).animateCollapsePanels(); } @Test @@ -644,9 +644,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); - Button fave = mNotificationInfo.findViewById(R.id.fave); - assertEquals(mContext.getString(R.string.notification_conversation_favorite), - fave.getText().toString()); + ImageButton fave = mNotificationInfo.findViewById(R.id.fave); + assertEquals(mContext.getString(R.string.notification_conversation_unfavorite), + fave.getContentDescription().toString()); fave.performClick(); mTestableLooper.processAllMessages(); @@ -677,9 +677,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, true); - Button fave = mNotificationInfo.findViewById(R.id.fave); - assertEquals(mContext.getString(R.string.notification_conversation_unfavorite), - fave.getText().toString()); + ImageButton fave = mNotificationInfo.findViewById(R.id.fave); + assertEquals(mContext.getString(R.string.notification_conversation_favorite), + fave.getContentDescription().toString()); fave.performClick(); mTestableLooper.processAllMessages(); @@ -692,34 +692,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase { } @Test - public void testDemote() throws Exception { - mNotificationInfo.bindNotification( - mShortcutManager, - mLauncherApps, - mMockPackageManager, - mMockINotificationManager, - mVisualStabilityManager, - TEST_PACKAGE_NAME, - mNotificationChannel, - mEntry, - null, - null, - null, - true); - - - ImageButton demote = mNotificationInfo.findViewById(R.id.demote); - demote.performClick(); - mTestableLooper.processAllMessages(); - - ArgumentCaptor<NotificationChannel> captor = - ArgumentCaptor.forClass(NotificationChannel.class); - verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage( - anyString(), anyInt(), captor.capture()); - assertTrue(captor.getValue().isDemoted()); - } - - @Test public void testMute_mute() throws Exception { mNotificationChannel.setImportance(IMPORTANCE_DEFAULT); mConversationChannel.setImportance(IMPORTANCE_DEFAULT); @@ -738,9 +710,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { null, true); - Button mute = mNotificationInfo.findViewById(R.id.mute); - assertEquals(mContext.getString(R.string.notification_conversation_mute), - mute.getText().toString()); + ImageButton mute = mNotificationInfo.findViewById(R.id.mute); + assertEquals(mContext.getString(R.string.notification_conversation_unmute), + mute.getContentDescription().toString()); mute.performClick(); mTestableLooper.processAllMessages(); @@ -774,9 +746,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { true); - Button mute = mNotificationInfo.findViewById(R.id.mute); - assertEquals(mContext.getString(R.string.notification_conversation_unmute), - mute.getText().toString()); + ImageButton mute = mNotificationInfo.findViewById(R.id.mute); + assertEquals(mContext.getString(R.string.notification_conversation_mute), + mute.getContentDescription().toString()); mute.performClick(); mTestableLooper.processAllMessages(); diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 54b420191deb..63dd99e215ab 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -244,6 +244,10 @@ message SystemMessage { // Package: android NOTE_SOFTAP_AUTO_DISABLED = 58; + // Notify the user that their admin has changed location settings. + // Package: android + NOTE_LOCATION_CHANGED = 59; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 565ee63d89ab..75ec4b04eeed 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1128,8 +1128,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ @Override public int getWindowIdForLeashToken(@NonNull IBinder token) { synchronized (mLock) { - // TODO: Add a method to lookup window ID by given leash token. - return -1; + return mA11yWindowManager.getWindowIdLocked(token); } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 7b495ce19015..ba29bc88f145 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -761,11 +761,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public int addAccessibilityInteractionConnection(IWindow windowToken, + public int addAccessibilityInteractionConnection(IWindow windowToken, IBinder leashToken, IAccessibilityInteractionConnection connection, String packageName, int userId) throws RemoteException { return mA11yWindowManager.addAccessibilityInteractionConnection( - windowToken, connection, packageName, userId); + windowToken, leashToken, connection, packageName, userId); } @Override @@ -2643,6 +2643,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override + public void associateEmbeddedHierarchy(@NonNull IBinder host, @NonNull IBinder embedded) { + synchronized (mLock) { + mA11yWindowManager.associateEmbeddedHierarchyLocked(host, embedded); + } + } + + @Override + public void disassociateEmbeddedHierarchy(@NonNull IBinder token) { + synchronized (mLock) { + mA11yWindowManager.disassociateEmbeddedHierarchyLocked(token); + } + } + + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; synchronized (mLock) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index 96e345ea9b89..d98e31eadb22 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -443,8 +443,8 @@ public class AccessibilitySecurityPolicy { return false; } } - // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy. - if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) { + if (mAccessibilityWindowManager.resolveParentWindowIdLocked(windowId) + == mAccessibilityWindowManager.getActiveWindowId(userId)) { return true; } return mAccessibilityWindowManager.findA11yWindowInfoByIdLocked(windowId) != null; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index a6041e0ee91c..8c0058130510 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -31,6 +31,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -100,6 +101,19 @@ public class AccessibilityWindowManager { new SparseArray<>(); /** + * Map of host view and embedded hierarchy, mapping from leash token of its ViewRootImpl. + * The key is the token from embedded hierarchy, and the value is the token from its host. + */ + private final ArrayMap<IBinder, IBinder> mHostEmbeddedMap = new ArrayMap<>(); + + /** + * Map of window id and view hierarchy. + * The key is the window id when the ViewRootImpl register to accessibility, and the value is + * its leash token. + */ + private final SparseArray<IBinder> mWindowIdMap = new SparseArray<>(); + + /** * This class implements {@link WindowManagerInternal.WindowsForAccessibilityCallback} to * receive {@link WindowInfo}s from window manager when there's an accessibility change in * window and holds window lists information per display. @@ -913,19 +927,21 @@ public class AccessibilityWindowManager { * Adds accessibility interaction connection according to given window token, package name and * window token. * - * @param windowToken The window token of accessibility interaction connection + * @param window The window token of accessibility interaction connection + * @param leashToken The leash token of accessibility interaction connection * @param connection The accessibility interaction connection * @param packageName The package name * @param userId The userId * @return The windowId of added connection * @throws RemoteException */ - public int addAccessibilityInteractionConnection(@NonNull IWindow windowToken, - @NonNull IAccessibilityInteractionConnection connection, @NonNull String packageName, - int userId) throws RemoteException { + public int addAccessibilityInteractionConnection(@NonNull IWindow window, + @NonNull IBinder leashToken, @NonNull IAccessibilityInteractionConnection connection, + @NonNull String packageName, int userId) throws RemoteException { final int windowId; boolean shouldComputeWindows = false; - final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken.asBinder()); + final IBinder token = window.asBinder(); + final int displayId = mWindowManagerInternal.getDisplayIdForWindow(token); synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -947,35 +963,33 @@ public class AccessibilityWindowManager { windowId, connection, packageName, resolvedUid, UserHandle.USER_ALL); wrapper.linkToDeath(); mGlobalInteractionConnections.put(windowId, wrapper); - mGlobalWindowTokens.put(windowId, windowToken.asBinder()); + mGlobalWindowTokens.put(windowId, token); if (DEBUG) { Slog.i(LOG_TAG, "Added global connection for pid:" + Binder.getCallingPid() - + " with windowId: " + windowId + " and token: " - + windowToken.asBinder()); + + " with windowId: " + windowId + " and token: " + token); } } else { RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection( windowId, connection, packageName, resolvedUid, resolvedUserId); wrapper.linkToDeath(); getInteractionConnectionsForUserLocked(resolvedUserId).put(windowId, wrapper); - getWindowTokensForUserLocked(resolvedUserId).put(windowId, windowToken.asBinder()); + getWindowTokensForUserLocked(resolvedUserId).put(windowId, token); if (DEBUG) { Slog.i(LOG_TAG, "Added user connection for pid:" + Binder.getCallingPid() - + " with windowId: " + windowId - + " and token: " + windowToken.asBinder()); + + " with windowId: " + windowId + " and token: " + token); } } if (isTrackingWindowsLocked(displayId)) { shouldComputeWindows = true; } + registerIdLocked(leashToken, windowId); } if (shouldComputeWindows) { mWindowManagerInternal.computeWindowsForAccessibility(displayId); } - mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata( - windowToken.asBinder(), windowId); + mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(token, windowId); return windowId; } @@ -1098,6 +1112,7 @@ public class AccessibilityWindowManager { * Invoked when accessibility interaction connection of window is removed. * * @param windowId Removed windowId + * @param binder Removed window token */ private void onAccessibilityInteractionConnectionRemovedLocked( int windowId, @Nullable IBinder binder) { @@ -1110,6 +1125,7 @@ public class AccessibilityWindowManager { mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata( binder, AccessibilityWindowInfo.UNDEFINED_WINDOW_ID); } + unregisterIdLocked(windowId); } /** @@ -1132,7 +1148,7 @@ public class AccessibilityWindowManager { * Returns the userId that owns the given window token, {@link UserHandle#USER_NULL} * if not found. * - * @param windowToken The winodw token + * @param windowToken The window token * @return The userId */ public int getWindowOwnerUserId(@NonNull IBinder windowToken) { @@ -1161,6 +1177,50 @@ public class AccessibilityWindowManager { } /** + * Establish the relationship between the host and the embedded view hierarchy. + * + * @param host The token of host hierarchy + * @param embedded The token of the embedded hierarchy + */ + public void associateEmbeddedHierarchyLocked(@NonNull IBinder host, @NonNull IBinder embedded) { + // Use embedded window as key, since one host window may have multiple embedded windows. + associateLocked(embedded, host); + } + + /** + * Clear the relationship by given token. + * + * @param token The token + */ + public void disassociateEmbeddedHierarchyLocked(@NonNull IBinder token) { + disassociateLocked(token); + } + + /** + * Gets the parent windowId of the window according to the specified windowId. + * + * @param windowId The windowId to check + * @return The windowId of the parent window, or self if no parent exists + */ + public int resolveParentWindowIdLocked(int windowId) { + final IBinder token = getTokenLocked(windowId); + if (token == null) { + return windowId; + } + final IBinder resolvedToken = resolveTopParentTokenLocked(token); + final int resolvedWindowId = getWindowIdLocked(resolvedToken); + return resolvedWindowId != -1 ? resolvedWindowId : windowId; + } + + private IBinder resolveTopParentTokenLocked(IBinder token) { + final IBinder hostToken = getHostTokenLocked(token); + if (hostToken == null) { + return token; + } + return resolveTopParentTokenLocked(hostToken); + } + + /** * Computes partial interactive region of given windowId. * * @param windowId The windowId @@ -1357,6 +1417,7 @@ public class AccessibilityWindowManager { */ @Nullable public AccessibilityWindowInfo findA11yWindowInfoByIdLocked(int windowId) { + windowId = resolveParentWindowIdLocked(windowId); final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId); if (observer != null) { return observer.findA11yWindowInfoByIdLocked(windowId); @@ -1584,6 +1645,88 @@ public class AccessibilityWindowManager { } /** + * Associate the token of the embedded view hierarchy to the host view hierarchy. + * + * @param embedded The leash token from the view root of embedded hierarchy + * @param host The leash token from the view root of host hierarchy + */ + void associateLocked(IBinder embedded, IBinder host) { + mHostEmbeddedMap.put(embedded, host); + } + + /** + * Clear the relationship of given token. + * + * @param token The leash token + */ + void disassociateLocked(IBinder token) { + mHostEmbeddedMap.remove(token); + for (int i = mHostEmbeddedMap.size() - 1; i >= 0; i--) { + if (mHostEmbeddedMap.valueAt(i).equals(token)) { + mHostEmbeddedMap.removeAt(i); + } + } + } + + /** + * Register the leash token with its windowId. + * + * @param token The token. + * @param windowId The windowID. + */ + void registerIdLocked(IBinder token, int windowId) { + mWindowIdMap.put(windowId, token); + } + + /** + * Unregister the windowId and also disassociate its token. + * + * @param windowId The windowID + */ + void unregisterIdLocked(int windowId) { + final IBinder token = mWindowIdMap.get(windowId); + if (token == null) { + return; + } + disassociateLocked(token); + mWindowIdMap.remove(windowId); + } + + /** + * Get the leash token by given windowID. + * + * @param windowId The windowID. + * @return The token, or {@code NULL} if this windowID doesn't exist + */ + IBinder getTokenLocked(int windowId) { + return mWindowIdMap.get(windowId); + } + + /** + * Get the windowId by given leash token. + * + * @param token The token + * @return The windowID, or -1 if the token doesn't exist + */ + int getWindowIdLocked(IBinder token) { + final int index = mWindowIdMap.indexOfValue(token); + if (index == -1) { + return index; + } + return mWindowIdMap.keyAt(index); + } + + /** + * Get the leash token of the host hierarchy by given token. + * + * @param token The token + * @return The token of host hierarchy, or {@code NULL} if no host exists + */ + IBinder getHostTokenLocked(IBinder token) { + return mHostEmbeddedMap.get(token); + } + + /** * Dumps all {@link AccessibilityWindowInfo}s here. */ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java index 5844f9873001..1c4db1214d3b 100644 --- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java +++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java @@ -18,12 +18,14 @@ package com.android.server.appprediction; import static android.Manifest.permission.MANAGE_APP_PREDICTIONS; import static android.Manifest.permission.PACKAGE_USAGE_STATS; +import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.content.Context.APP_PREDICTION_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManagerInternal; import android.app.prediction.AppPredictionContext; import android.app.prediction.AppPredictionSessionId; import android.app.prediction.AppTargetEvent; @@ -34,7 +36,6 @@ import android.content.pm.ParceledListSlice; import android.os.Binder; import android.os.ResultReceiver; import android.os.ShellCallback; -import android.os.UserHandle; import android.util.Slog; import com.android.server.LocalServices; @@ -108,21 +109,21 @@ public class AppPredictionManagerService extends @Override public void createPredictionSession(@NonNull AppPredictionContext context, @NonNull AppPredictionSessionId sessionId) { - runForUserLocked("createPredictionSession", + runForUserLocked("createPredictionSession", sessionId, (service) -> service.onCreatePredictionSessionLocked(context, sessionId)); } @Override public void notifyAppTargetEvent(@NonNull AppPredictionSessionId sessionId, @NonNull AppTargetEvent event) { - runForUserLocked("notifyAppTargetEvent", + runForUserLocked("notifyAppTargetEvent", sessionId, (service) -> service.notifyAppTargetEventLocked(sessionId, event)); } @Override public void notifyLaunchLocationShown(@NonNull AppPredictionSessionId sessionId, @NonNull String launchLocation, @NonNull ParceledListSlice targetIds) { - runForUserLocked("notifyLaunchLocationShown", (service) -> + runForUserLocked("notifyLaunchLocationShown", sessionId, (service) -> service.notifyLaunchLocationShownLocked(sessionId, launchLocation, targetIds)); } @@ -130,32 +131,32 @@ public class AppPredictionManagerService extends public void sortAppTargets(@NonNull AppPredictionSessionId sessionId, @NonNull ParceledListSlice targets, IPredictionCallback callback) { - runForUserLocked("sortAppTargets", + runForUserLocked("sortAppTargets", sessionId, (service) -> service.sortAppTargetsLocked(sessionId, targets, callback)); } @Override public void registerPredictionUpdates(@NonNull AppPredictionSessionId sessionId, @NonNull IPredictionCallback callback) { - runForUserLocked("registerPredictionUpdates", + runForUserLocked("registerPredictionUpdates", sessionId, (service) -> service.registerPredictionUpdatesLocked(sessionId, callback)); } public void unregisterPredictionUpdates(@NonNull AppPredictionSessionId sessionId, @NonNull IPredictionCallback callback) { - runForUserLocked("unregisterPredictionUpdates", + runForUserLocked("unregisterPredictionUpdates", sessionId, (service) -> service.unregisterPredictionUpdatesLocked(sessionId, callback)); } @Override public void requestPredictionUpdate(@NonNull AppPredictionSessionId sessionId) { - runForUserLocked("requestPredictionUpdate", + runForUserLocked("requestPredictionUpdate", sessionId, (service) -> service.requestPredictionUpdateLocked(sessionId)); } @Override public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) { - runForUserLocked("onDestroyPredictionSession", + runForUserLocked("onDestroyPredictionSession", sessionId, (service) -> service.onDestroyPredictionSessionLocked(sessionId)); } @@ -167,9 +168,12 @@ public class AppPredictionManagerService extends .exec(this, in, out, err, args, callback, resultReceiver); } - private void runForUserLocked(@NonNull String func, - @NonNull Consumer<AppPredictionPerUserService> c) { - final int userId = UserHandle.getCallingUserId(); + private void runForUserLocked(@NonNull final String func, + @NonNull final AppPredictionSessionId sessionId, + @NonNull final Consumer<AppPredictionPerUserService> c) { + ActivityManagerInternal am = LocalServices.getService(ActivityManagerInternal.class); + final int userId = am.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + sessionId.getUserId(), false, ALLOW_NON_FULL, null, null); Context ctx = getContext(); if (!(ctx.checkCallingPermission(PACKAGE_USAGE_STATS) == PERMISSION_GRANTED diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index e3d2dcc8141f..6247a635233a 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -1080,12 +1080,8 @@ public class UserBackupManagerService { } } - public Map<String, Set<String>> getExcludedRestoreKeys(String... packages) { - return mBackupPreferences.getExcludedRestoreKeysForPackages(packages); - } - - public Map<String, Set<String>> getAllExcludedRestoreKeys() { - return mBackupPreferences.getAllExcludedRestoreKeys(); + public Set<String> getExcludedRestoreKeys(String packageName) { + return mBackupPreferences.getExcludedRestoreKeysForPackage(packageName); } /** Used for generating random salts or passwords. */ @@ -3356,8 +3352,7 @@ public class UserBackupManagerService { restoreSet, packageName, token, - listener, - getExcludedRestoreKeys(packageName)); + listener); mBackupHandler.sendMessage(msg); } catch (Exception e) { // Calling into the transport broke; back off and proceed with the installation. diff --git a/services/backup/java/com/android/server/backup/UserBackupPreferences.java b/services/backup/java/com/android/server/backup/UserBackupPreferences.java index 41b9719d273b..bb8bf52187c5 100644 --- a/services/backup/java/com/android/server/backup/UserBackupPreferences.java +++ b/services/backup/java/com/android/server/backup/UserBackupPreferences.java @@ -48,16 +48,7 @@ public class UserBackupPreferences { mEditor.commit(); } - Map<String, Set<String>> getExcludedRestoreKeysForPackages(String... packages) { - Map<String, Set<String>> excludedKeys = new HashMap<>(); - for (String packageName : packages) { - excludedKeys.put(packageName, - mPreferences.getStringSet(packageName, Collections.emptySet())); - } - return excludedKeys; - } - - Map<String, Set<String>> getAllExcludedRestoreKeys() { - return (Map<String, Set<String>>) mPreferences.getAll(); + Set<String> getExcludedRestoreKeysForPackage(String packageName) { + return mPreferences.getStringSet(packageName, Collections.emptySet()); } } diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java index 05396f36b364..87a8e4982529 100644 --- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java +++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java @@ -299,8 +299,7 @@ public class BackupHandler extends Handler { params.pmToken, params.isSystemRestore, params.filterSet, - params.listener, - params.excludedKeys); + params.listener); synchronized (backupManagerService.getPendingRestores()) { if (backupManagerService.isRestoreInProgress()) { diff --git a/services/backup/java/com/android/server/backup/params/RestoreParams.java b/services/backup/java/com/android/server/backup/params/RestoreParams.java index 09b7e3535e2e..a6fea6cc75a0 100644 --- a/services/backup/java/com/android/server/backup/params/RestoreParams.java +++ b/services/backup/java/com/android/server/backup/params/RestoreParams.java @@ -37,7 +37,6 @@ public class RestoreParams { public final boolean isSystemRestore; @Nullable public final String[] filterSet; public final OnTaskFinishedListener listener; - public final Map<String, Set<String>> excludedKeys; /** * No kill after restore. @@ -48,8 +47,7 @@ public class RestoreParams { IBackupManagerMonitor monitor, long token, PackageInfo packageInfo, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { return new RestoreParams( transportClient, observer, @@ -59,8 +57,7 @@ public class RestoreParams { /* pmToken */ 0, /* isSystemRestore */ false, /* filterSet */ null, - listener, - excludedKeys); + listener); } /** @@ -73,8 +70,7 @@ public class RestoreParams { long token, String packageName, int pmToken, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { String[] filterSet = {packageName}; return new RestoreParams( transportClient, @@ -85,8 +81,7 @@ public class RestoreParams { pmToken, /* isSystemRestore */ false, filterSet, - listener, - excludedKeys); + listener); } /** @@ -97,8 +92,7 @@ public class RestoreParams { IRestoreObserver observer, IBackupManagerMonitor monitor, long token, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { return new RestoreParams( transportClient, observer, @@ -108,8 +102,7 @@ public class RestoreParams { /* pmToken */ 0, /* isSystemRestore */ true, /* filterSet */ null, - listener, - excludedKeys); + listener); } /** @@ -122,8 +115,7 @@ public class RestoreParams { long token, String[] filterSet, boolean isSystemRestore, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { return new RestoreParams( transportClient, observer, @@ -133,8 +125,7 @@ public class RestoreParams { /* pmToken */ 0, isSystemRestore, filterSet, - listener, - excludedKeys); + listener); } private RestoreParams( @@ -146,8 +137,7 @@ public class RestoreParams { int pmToken, boolean isSystemRestore, @Nullable String[] filterSet, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { this.transportClient = transportClient; this.observer = observer; this.monitor = monitor; @@ -157,6 +147,5 @@ public class RestoreParams { this.isSystemRestore = isSystemRestore; this.filterSet = filterSet; this.listener = listener; - this.excludedKeys = excludedKeys; } } diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java index c0f76c39e04f..5a57cdc39402 100644 --- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java +++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java @@ -178,8 +178,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { observer, monitor, token, - listener, - mBackupManagerService.getAllExcludedRestoreKeys()), + listener), "RestoreSession.restoreAll()"); } finally { Binder.restoreCallingIdentity(oldId); @@ -272,8 +271,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { token, packages, /* isSystemRestore */ packages.length > 1, - listener, - mBackupManagerService.getExcludedRestoreKeys(packages)), + listener), "RestoreSession.restorePackages(" + packages.length + " packages)"); } finally { Binder.restoreCallingIdentity(oldId); @@ -365,8 +363,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub { monitor, token, app, - listener, - mBackupManagerService.getExcludedRestoreKeys(app.packageName)), + listener), "RestoreSession.restorePackage(" + packageName + ")"); } finally { Binder.restoreCallingIdentity(oldId); diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index f90d936a5f4d..3c37f737f8be 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -155,8 +155,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // When finished call listener private final OnTaskFinishedListener mListener; - private final Map<String, Set<String>> mExcludedKeys; - // Key/value: bookkeeping about staged data and files for agent access private File mBackupDataName; private File mStageName; @@ -168,14 +166,14 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { private final BackupAgentTimeoutParameters mAgentTimeoutParameters; @VisibleForTesting - PerformUnifiedRestoreTask(Map<String, Set<String>> excludedKeys) { - mExcludedKeys = excludedKeys; + PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) { mListener = null; mAgentTimeoutParameters = null; mTransportClient = null; mTransportManager = null; mEphemeralOpToken = 0; mUserId = 0; + this.backupManagerService = backupManagerService; } // This task can assume that the wakelock is properly held for it and doesn't have to worry @@ -190,8 +188,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { int pmToken, boolean isFullSystemRestore, @Nullable String[] filterSet, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { this.backupManagerService = backupManagerService; mUserId = backupManagerService.getUserId(); mTransportManager = backupManagerService.getTransportManager(); @@ -213,8 +210,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { backupManagerService.getAgentTimeoutParameters(), "Timeout parameters cannot be null"); - mExcludedKeys = excludedKeys; - if (targetPackage != null) { // Single package restore mAcceptSet = new ArrayList<>(); @@ -791,8 +786,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { !getExcludedKeysForPackage(PLATFORM_PACKAGE_NAME).isEmpty(); } - private Set<String> getExcludedKeysForPackage(String packageName) { - return mExcludedKeys.getOrDefault(packageName, Collections.emptySet()); + @VisibleForTesting + Set<String> getExcludedKeysForPackage(String packageName) { + return backupManagerService.getExcludedRestoreKeys(packageName); } @VisibleForTesting diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 63cddac0bfba..2c91a11c358c 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -43,6 +43,7 @@ import android.location.Criteria; import android.location.GeocoderParams; import android.location.Geofence; import android.location.GnssMeasurementCorrections; +import android.location.GnssRequest; import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; @@ -2287,12 +2288,14 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener, - String packageName, String featureId, String listenerIdentifier) { + public boolean addGnssMeasurementsListener(@Nullable GnssRequest request, + IGnssMeasurementsListener listener, + String packageName, String featureId, + String listenerIdentifier) { Objects.requireNonNull(listenerIdentifier); return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener( - listener, packageName, featureId, listenerIdentifier); + request, listener, packageName, featureId, listenerIdentifier); } @Override @@ -2419,6 +2422,17 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override + public void setLocationEnabledForUser(boolean enabled, int userId) { + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS, + null); + } + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS, + "Requires WRITE_SECURE_SETTINGS permission"); + mSettingsHelper.setLocationEnabled(enabled, userId); + } + + @Override public boolean isLocationEnabledForUser(int userId) { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, "isLocationEnabledForUser", null); diff --git a/services/core/java/com/android/server/LocationManagerServiceUtils.java b/services/core/java/com/android/server/LocationManagerServiceUtils.java index 372e91e41772..ba1c81cdd975 100644 --- a/services/core/java/com/android/server/LocationManagerServiceUtils.java +++ b/services/core/java/com/android/server/LocationManagerServiceUtils.java @@ -17,6 +17,7 @@ package com.android.server; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -37,22 +38,31 @@ public class LocationManagerServiceUtils { /** * Listener that can be linked to a binder. * @param <TListener> listener type + * @param <TRequest> request type */ - public static class LinkedListener<TListener> extends + public static class LinkedListener<TRequest, TListener> extends LinkedListenerBase { + @Nullable protected final TRequest mRequest; private final TListener mListener; private final Consumer<TListener> mBinderDeathCallback; public LinkedListener( + @Nullable TRequest request, @NonNull TListener listener, String listenerName, @NonNull CallerIdentity callerIdentity, @NonNull Consumer<TListener> binderDeathCallback) { super(callerIdentity, listenerName); mListener = listener; + mRequest = request; mBinderDeathCallback = binderDeathCallback; } + @Nullable + public TRequest getRequest() { + return mRequest; + } + @Override public void binderDied() { if (D) Log.d(TAG, "Remote " + mListenerName + " died."); diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index b994e6c58e64..f2d5a9b685f4 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -302,15 +302,19 @@ final class UiModeManagerService extends SystemService { private final ContentObserver mDarkThemeObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Uri uri) { - int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE, - mNightMode, 0); - if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) { - mode = MODE_NIGHT_YES; - } - SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode)); + updateSystemProperties(); } }; + private void updateSystemProperties() { + int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE, + mNightMode, 0); + if (mode == MODE_NIGHT_AUTO || mode == MODE_NIGHT_CUSTOM) { + mode = MODE_NIGHT_YES; + } + SystemProperties.set(SYSTEM_PROPERTY_DEVICE_THEME, Integer.toString(mode)); + } + @Override public void onSwitchUser(int userHandle) { super.onSwitchUser(userHandle); @@ -392,6 +396,7 @@ final class UiModeManagerService extends SystemService { context.getContentResolver().registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE), false, mDarkThemeObserver, 0); + updateSystemProperties(); } @VisibleForTesting diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 0686e3e99b2f..b6dc3de908f0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -466,9 +466,18 @@ public class ActivityManagerService extends IActivityManager.Stub // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real. static final int PROC_START_TIMEOUT = 10*1000; + // How long we wait for an attached process to publish its content providers + // before we decide it must be hung. + static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000; + // How long we wait to kill an application zygote, after the last process using // it has gone away. static final int KILL_APP_ZYGOTE_DELAY_MS = 5 * 1000; + /** + * How long we wait for an provider to be published. Should be longer than + * {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT}. + */ + static final int CONTENT_PROVIDER_WAIT_TIMEOUT = 20 * 1000; // How long we wait for a launched process to attach to the activity manager // before we decide it's never going to come up for real, when the process was @@ -4925,8 +4934,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (providers != null && checkAppInLaunchingProvidersLocked(app)) { Message msg = mHandler.obtainMessage(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG); msg.obj = app; - mHandler.sendMessageDelayed(msg, - ContentResolver.CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS); + mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT); } checkTime(startTime, "attachApplicationLocked: before bindApplication"); @@ -7193,8 +7201,7 @@ public class ActivityManagerService extends IActivityManager.Stub } // Wait for the provider to be published... - final long timeout = - SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS; + final long timeout = SystemClock.uptimeMillis() + CONTENT_PROVIDER_WAIT_TIMEOUT; boolean timedOut = false; synchronized (cpr) { while (cpr.provider == null) { @@ -7231,14 +7238,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } if (timedOut) { - // Note we do it after releasing the lock. + // Note we do it afer releasing the lock. String callerName = "unknown"; - if (caller != null) { - synchronized (this) { - final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller); - if (record != null) { - callerName = record.processName; - } + synchronized (this) { + final ProcessRecord record = mProcessList.getLRURecordForAppLocked(caller); + if (record != null) { + callerName = record.processName; } } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 145f91bdb0b3..789f7199948d 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -798,7 +798,6 @@ class AppErrors { boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; - final String packageName; final int userId; synchronized (mService) { final ProcessRecord proc = data.proc; @@ -807,7 +806,6 @@ class AppErrors { Slog.e(TAG, "handleShowAppErrorUi: proc is null"); return; } - packageName = proc.info.packageName; userId = proc.userId; if (proc.getDialogController().hasCrashDialogs()) { Slog.e(TAG, "App already has crash dialog: " + proc); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index f3d8bc8cb64f..e63da9b1e80b 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1910,8 +1910,7 @@ class ProcessRecord implements WindowProcessListener { mWaitDialog = null; } - void forAllDialogs(List<? extends BaseErrorDialog> dialogs, - Consumer<BaseErrorDialog> c) { + void forAllDialogs(List<? extends BaseErrorDialog> dialogs, Consumer<BaseErrorDialog> c) { for (int i = dialogs.size() - 1; i >= 0; i--) { c.accept(dialogs.get(i)); } @@ -1920,42 +1919,72 @@ class ProcessRecord implements WindowProcessListener { void showCrashDialogs(AppErrorDialog.Data data) { List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */); mCrashDialogs = new ArrayList<>(); - for (int i = contexts.size() - 1; i >= 0; i--) { final Context c = contexts.get(i); mCrashDialogs.add(new AppErrorDialog(c, mService, data)); } - mService.mUiHandler.post(() -> mCrashDialogs.forEach(Dialog::show)); + mService.mUiHandler.post(() -> { + List<AppErrorDialog> dialogs; + synchronized (mService) { + dialogs = mCrashDialogs; + } + if (dialogs != null) { + forAllDialogs(dialogs, Dialog::show); + } + }); } void showAnrDialogs(AppNotRespondingDialog.Data data) { List<Context> contexts = getDisplayContexts(isSilentAnr() /* lastUsedOnly */); mAnrDialogs = new ArrayList<>(); - for (int i = contexts.size() - 1; i >= 0; i--) { final Context c = contexts.get(i); mAnrDialogs.add(new AppNotRespondingDialog(mService, c, data)); } - mService.mUiHandler.post(() -> mAnrDialogs.forEach(Dialog::show)); + mService.mUiHandler.post(() -> { + List<AppNotRespondingDialog> dialogs; + synchronized (mService) { + dialogs = mAnrDialogs; + } + if (dialogs != null) { + forAllDialogs(dialogs, Dialog::show); + } + }); } void showViolationDialogs(AppErrorResult res) { List<Context> contexts = getDisplayContexts(false /* lastUsedOnly */); mViolationDialogs = new ArrayList<>(); - for (int i = contexts.size() - 1; i >= 0; i--) { final Context c = contexts.get(i); mViolationDialogs.add( new StrictModeViolationDialog(c, mService, res, ProcessRecord.this)); } - mService.mUiHandler.post(() -> mViolationDialogs.forEach(Dialog::show)); + mService.mUiHandler.post(() -> { + List<StrictModeViolationDialog> dialogs; + synchronized (mService) { + dialogs = mViolationDialogs; + } + if (dialogs != null) { + forAllDialogs(dialogs, Dialog::show); + } + }); } void showDebugWaitingDialogs() { List<Context> contexts = getDisplayContexts(true /* lastUsedOnly */); final Context c = contexts.get(0); mWaitDialog = new AppWaitingForDebuggerDialog(mService, c, ProcessRecord.this); - mService.mUiHandler.post(() -> mWaitDialog.show()); + + mService.mUiHandler.post(() -> { + Dialog dialog; + synchronized (mService) { + dialog = mWaitDialog; + } + if (dialog != null) { + dialog.show(); + } + }); } /** diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index c75ee04543e3..b2d0441bb2f8 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -414,6 +414,7 @@ class UserController implements Handler.Callback { Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null); intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT + | Intent.FLAG_RECEIVER_OFFLOAD | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); mInjector.broadcastIntent(intent, null, resultTo, 0, null, null, new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED}, diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 1f998c377c7b..ef1bc835dea5 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -702,6 +702,10 @@ public class AudioDeviceInventory { delay = 0; } mDeviceBroker.postSetHearingAidConnectionState(state, device, delay); + if (state == BluetoothHearingAid.STATE_CONNECTED) { + mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE, + "HEARING_AID set to CONNECTED"); + } return delay; } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 07fc9b7a7e5f..26c94c5ab978 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -25,6 +25,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import static android.hardware.biometrics.BiometricManager.Authenticators; +import android.annotation.IntDef; import android.app.ActivityManager; import android.app.IActivityManager; import android.app.UserSwitchObserver; @@ -83,6 +84,25 @@ public class BiometricService extends SystemService { static final String TAG = "BiometricService"; private static final boolean DEBUG = true; + private static final int BIOMETRIC_NO_HARDWARE = 0; + private static final int BIOMETRIC_OK = 1; + private static final int BIOMETRIC_DISABLED_BY_DEVICE_POLICY = 2; + private static final int BIOMETRIC_INSUFFICIENT_STRENGTH = 3; + private static final int BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE = 4; + private static final int BIOMETRIC_HARDWARE_NOT_DETECTED = 5; + private static final int BIOMETRIC_NOT_ENROLLED = 6; + private static final int BIOMETRIC_NOT_ENABLED_FOR_APPS = 7; + + @IntDef({BIOMETRIC_NO_HARDWARE, + BIOMETRIC_OK, + BIOMETRIC_DISABLED_BY_DEVICE_POLICY, + BIOMETRIC_INSUFFICIENT_STRENGTH, + BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE, + BIOMETRIC_HARDWARE_NOT_DETECTED, + BIOMETRIC_NOT_ENROLLED, + BIOMETRIC_NOT_ENABLED_FOR_APPS}) + @interface BiometricStatus {} + private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2; private static final int MSG_ON_AUTHENTICATION_REJECTED = 3; private static final int MSG_ON_ERROR = 4; @@ -206,7 +226,7 @@ public class BiometricService extends SystemService { } boolean isAllowDeviceCredential() { - return Utils.isDeviceCredentialAllowed(mBundle); + return Utils.isCredentialRequested(mBundle); } } @@ -372,16 +392,20 @@ public class BiometricService extends SystemService { * strength. * @return a bitfield, see {@link Authenticators} */ - public int getActualStrength() { + int getActualStrength() { return OEMStrength | updatedStrength; } + boolean isDowngraded() { + return OEMStrength != updatedStrength; + } + /** * Stores the updated strength, which takes effect whenever {@link #getActualStrength()} * is checked. * @param newStrength */ - public void updateStrength(int newStrength) { + void updateStrength(int newStrength) { String log = "updateStrength: Before(" + toString() + ")"; updatedStrength = newStrength; log += " After(" + toString() + ")"; @@ -1007,6 +1031,79 @@ public class BiometricService extends SystemService { return isBiometricDisabled; } + private static int biometricStatusToBiometricConstant(@BiometricStatus int status) { + switch (status) { + case BIOMETRIC_NO_HARDWARE: + return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT; + case BIOMETRIC_OK: + return BiometricConstants.BIOMETRIC_SUCCESS; + case BIOMETRIC_DISABLED_BY_DEVICE_POLICY: + return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; + case BIOMETRIC_INSUFFICIENT_STRENGTH: + return BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT; + case BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE: + return BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED; + case BIOMETRIC_HARDWARE_NOT_DETECTED: + return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; + case BIOMETRIC_NOT_ENROLLED: + return BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS; + case BIOMETRIC_NOT_ENABLED_FOR_APPS: + return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; + default: + return BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE; + } + } + + /** + * Returns the status of the authenticator, with errors returned in a specific priority order. + * For example, {@link #BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE} is only returned + * if it has enrollments, and is enabled for apps. + * + * We should only return the modality if the authenticator should be exposed. e.g. + * BIOMETRIC_NOT_ENROLLED_FOR_APPS should not expose the authenticator's type. + * + * @return A Pair with `first` being modality, and `second` being @BiometricStatus + */ + private Pair<Integer, Integer> getStatusForBiometricAuthenticator( + AuthenticatorWrapper authenticator, int userId, String opPackageName, + boolean checkDevicePolicyManager, int requestedStrength) { + if (checkDevicePolicyManager) { + if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) { + return new Pair<>(TYPE_NONE, BIOMETRIC_DISABLED_BY_DEVICE_POLICY); + } + } + + final boolean wasStrongEnough = + Utils.isAtLeastStrength(authenticator.OEMStrength, requestedStrength); + final boolean isStrongEnough = + Utils.isAtLeastStrength(authenticator.getActualStrength(), requestedStrength); + + if (wasStrongEnough && !isStrongEnough) { + return new Pair<>(authenticator.modality, + BIOMETRIC_INSUFFICIENT_STRENGTH_AFTER_DOWNGRADE); + } else if (!wasStrongEnough) { + return new Pair<>(TYPE_NONE, BIOMETRIC_INSUFFICIENT_STRENGTH); + } + + try { + if (!authenticator.impl.isHardwareDetected(opPackageName)) { + return new Pair<>(authenticator.modality, BIOMETRIC_HARDWARE_NOT_DETECTED); + } + + if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) { + return new Pair<>(authenticator.modality, BIOMETRIC_NOT_ENROLLED); + } + } catch (RemoteException e) { + return new Pair<>(authenticator.modality, BIOMETRIC_HARDWARE_NOT_DETECTED); + } + + if (!isEnabledForApp(authenticator.modality, userId)) { + return new Pair<>(TYPE_NONE, BIOMETRIC_NOT_ENABLED_FOR_APPS); + } + + return new Pair<>(authenticator.modality, BIOMETRIC_OK); + } + /** * Depending on the requested authentication (credential/biometric combination), checks their * availability. @@ -1029,10 +1126,9 @@ public class BiometricService extends SystemService { private Pair<Integer, Integer> checkAndGetAuthenticators(int userId, Bundle bundle, String opPackageName, boolean checkDevicePolicyManager) throws RemoteException { - final boolean biometricRequested = Utils.isBiometricAllowed(bundle); - final boolean credentialRequested = Utils.isDeviceCredentialAllowed(bundle); + final boolean biometricRequested = Utils.isBiometricRequested(bundle); + final boolean credentialRequested = Utils.isCredentialRequested(bundle); - final boolean biometricOk; final boolean credentialOk = mTrustManager.isDeviceSecure(userId); // Assuming that biometric authenticators are listed in priority-order, the rest of this @@ -1041,96 +1137,56 @@ public class BiometricService extends SystemService { // the correct error. Error strings that are modality-specific should also respect the // priority-order. - // Find first biometric authenticator that's strong enough, detected, enrolled, and enabled. - boolean disabledByDevicePolicy = false; - boolean hasSufficientStrength = false; - boolean isHardwareDetected = false; - boolean hasTemplatesEnrolled = false; - boolean enabledForApps = false; + int firstBiometricModality = TYPE_NONE; + @BiometricStatus int firstBiometricStatus = BIOMETRIC_NO_HARDWARE; + + int biometricModality = TYPE_NONE; + @BiometricStatus int biometricStatus = BIOMETRIC_NO_HARDWARE; - int modality = TYPE_NONE; - int firstHwAvailable = TYPE_NONE; for (AuthenticatorWrapper authenticator : mAuthenticators) { - final int actualStrength = authenticator.getActualStrength(); final int requestedStrength = Utils.getPublicBiometricStrength(bundle); + Pair<Integer, Integer> result = getStatusForBiometricAuthenticator( + authenticator, userId, opPackageName, checkDevicePolicyManager, + requestedStrength); - if (isBiometricDisabledByDevicePolicy(authenticator.modality, userId)) { - disabledByDevicePolicy = true; - continue; - } - disabledByDevicePolicy = false; - - if (!Utils.isAtLeastStrength(actualStrength, requestedStrength)) { - continue; - } - hasSufficientStrength = true; + biometricStatus = result.second; - if (!authenticator.impl.isHardwareDetected(opPackageName)) { - continue; - } - isHardwareDetected = true; + Slog.d(TAG, "Authenticator ID: " + authenticator.id + + " Modality: " + authenticator.modality + + " ReportedModality: " + result.first + + " Status: " + biometricStatus); - if (firstHwAvailable == TYPE_NONE) { - // Store the first one since we want to return the error in correct - // priority order. - firstHwAvailable = authenticator.modality; + if (firstBiometricModality == TYPE_NONE) { + firstBiometricModality = result.first; + firstBiometricStatus = biometricStatus; } - if (!authenticator.impl.hasEnrolledTemplates(userId, opPackageName)) { - continue; - } - hasTemplatesEnrolled = true; - - if (!isEnabledForApp(authenticator.modality, userId)) { - continue; + if (biometricStatus == BIOMETRIC_OK) { + biometricModality = result.first; + break; } - enabledForApps = true; - modality = authenticator.modality; - break; } - biometricOk = !disabledByDevicePolicy - && hasSufficientStrength && isHardwareDetected - && hasTemplatesEnrolled && enabledForApps; - - Slog.d(TAG, "checkAndGetAuthenticators: user=" + userId - + " checkDevicePolicyManager=" + checkDevicePolicyManager - + " isHardwareDetected=" + isHardwareDetected - + " hasTemplatesEnrolled=" + hasTemplatesEnrolled - + " enabledForApps=" + enabledForApps - + " disabledByDevicePolicy=" + disabledByDevicePolicy); - if (biometricRequested && credentialRequested) { - if (credentialOk || biometricOk) { - if (!biometricOk) { + if (credentialOk || biometricStatus == BIOMETRIC_OK) { + if (biometricStatus != BIOMETRIC_OK) { // If there's a problem with biometrics but device credential is // allowed, only show credential UI. bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, Authenticators.DEVICE_CREDENTIAL); } - return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); + return new Pair<>(biometricModality, BiometricConstants.BIOMETRIC_SUCCESS); } else { - return new Pair<>(firstHwAvailable, + return new Pair<>(firstBiometricModality, BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); } } else if (biometricRequested) { - if (biometricOk) { - return new Pair<>(modality, BiometricConstants.BIOMETRIC_SUCCESS); - } else if (disabledByDevicePolicy) { - return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); - } else if (!hasSufficientStrength) { - return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT); - } else if (!isHardwareDetected) { - return new Pair<>(firstHwAvailable, - BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); - } else if (!hasTemplatesEnrolled) { - return new Pair<>(firstHwAvailable, - BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS); - } else if (!enabledForApps) { - return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); + if (biometricStatus == BIOMETRIC_OK) { + return new Pair<>(biometricModality, + biometricStatusToBiometricConstant(biometricStatus)); } else { - Slog.e(TAG, "Unexpected case"); - return new Pair<>(TYPE_NONE, BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE); + return new Pair<>(firstBiometricModality, + biometricStatusToBiometricConstant(firstBiometricStatus)); } } else if (credentialRequested) { if (credentialOk) { @@ -1403,16 +1459,14 @@ public class BiometricService extends SystemService { return; } - if (acquiredInfo != BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { - if (message == null) { - Slog.w(TAG, "Ignoring null message: " + acquiredInfo); - return; - } - try { - mStatusBarService.onBiometricHelp(message); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } + if (message == null) { + Slog.w(TAG, "Ignoring null message: " + acquiredInfo); + return; + } + try { + mStatusBarService.onBiometricHelp(message); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); } } diff --git a/services/core/java/com/android/server/biometrics/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/LoggableMonitor.java index c03c77f41f57..c50ab175199d 100644 --- a/services/core/java/com/android/server/biometrics/LoggableMonitor.java +++ b/services/core/java/com/android/server/biometrics/LoggableMonitor.java @@ -20,6 +20,7 @@ import android.content.Context; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; @@ -69,8 +70,12 @@ public abstract class LoggableMonitor { protected final void logOnAcquired(Context context, int acquiredInfo, int vendorCode, int targetUserId) { - if (statsModality() == BiometricsProtoEnums.MODALITY_FACE) { - if (acquiredInfo == FaceManager.FACE_ACQUIRED_START) { + + final boolean isFace = statsModality() == BiometricsProtoEnums.MODALITY_FACE; + final boolean isFingerprint = statsModality() == BiometricsProtoEnums.MODALITY_FINGERPRINT; + if (isFace || isFingerprint) { + if ((isFingerprint && acquiredInfo == FingerprintManager.FINGERPRINT_ACQUIRED_START) + || (isFace && acquiredInfo == FaceManager.FACE_ACQUIRED_START)) { mFirstAcquireTimeMs = System.currentTimeMillis(); } } else if (acquiredInfo == BiometricConstants.BIOMETRIC_ACQUIRED_GOOD) { @@ -124,7 +129,7 @@ public abstract class LoggableMonitor { error, vendorCode, Utils.isDebugEnabled(context, targetUserId), - latency); + sanitizeLatency(latency)); } protected final void logOnAuthenticated(Context context, boolean authenticated, @@ -165,7 +170,7 @@ public abstract class LoggableMonitor { statsClient(), requireConfirmation, authState, - latency, + sanitizeLatency(latency), Utils.isDebugEnabled(context, targetUserId)); } @@ -183,8 +188,16 @@ public abstract class LoggableMonitor { FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED, statsModality(), targetUserId, - latency, + sanitizeLatency(latency), enrollSuccessful); } + private long sanitizeLatency(long latency) { + if (latency < 0) { + Slog.w(TAG, "found a negative latency : " + latency); + return -1; + } + return latency; + } + } diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java index 2d4ab6308a8b..8f3fd36f411b 100644 --- a/services/core/java/com/android/server/biometrics/Utils.java +++ b/services/core/java/com/android/server/biometrics/Utils.java @@ -80,7 +80,7 @@ public class Utils { * @param authenticators composed of one or more values from {@link Authenticators} * @return true if device credential is allowed. */ - public static boolean isDeviceCredentialAllowed(@Authenticators.Types int authenticators) { + public static boolean isCredentialRequested(@Authenticators.Types int authenticators) { return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0; } @@ -88,8 +88,8 @@ public class Utils { * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)} * @return true if device credential is allowed. */ - public static boolean isDeviceCredentialAllowed(Bundle bundle) { - return isDeviceCredentialAllowed(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); + public static boolean isCredentialRequested(Bundle bundle) { + return isCredentialRequested(bundle.getInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED)); } /** @@ -120,7 +120,7 @@ public class Utils { * @param bundle should be first processed by {@link #combineAuthenticatorBundles(Bundle)} * @return true if biometric authentication is allowed. */ - public static boolean isBiometricAllowed(Bundle bundle) { + public static boolean isBiometricRequested(Bundle bundle) { return getPublicBiometricStrength(bundle) != 0; } @@ -169,7 +169,7 @@ public class Utils { // should be set. final int biometricBits = authenticators & Authenticators.BIOMETRIC_MIN_STRENGTH; if (biometricBits == Authenticators.EMPTY_SET - && isDeviceCredentialAllowed(authenticators)) { + && isCredentialRequested(authenticators)) { return true; } else if (biometricBits == Authenticators.BIOMETRIC_STRONG) { return true; @@ -209,6 +209,9 @@ public class Utils { case BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT: biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE; break; + case BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED: + biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED; + break; default: Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode); biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE; diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java index 57d1867b3aca..0a6198863b00 100644 --- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java @@ -41,7 +41,7 @@ import android.hardware.biometrics.IBiometricNativeHandle; import android.hardware.biometrics.IBiometricServiceLockoutResetCallback; import android.hardware.biometrics.IBiometricServiceReceiverInternal; import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; -import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprintClientCallback; +import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.IFingerprintClientActiveCallback; @@ -601,6 +601,11 @@ public class FingerprintService extends BiometricServiceBase { @Override public void onAcquired(final long deviceId, final int acquiredInfo, final int vendorCode) { + onAcquired_2_2(deviceId, acquiredInfo, vendorCode); + } + + @Override + public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) { mHandler.post(() -> { FingerprintService.super.handleAcquired(deviceId, acquiredInfo, vendorCode); }); diff --git a/services/core/java/com/android/server/compat/TEST_MAPPING b/services/core/java/com/android/server/compat/TEST_MAPPING new file mode 100644 index 000000000000..0c30c790c5dd --- /dev/null +++ b/services/core/java/com/android/server/compat/TEST_MAPPING @@ -0,0 +1,21 @@ +{ + "presubmit": [ + // Unit tests + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.compat" + } + ] + }, + // Tests for the TestRule + { + "name": "PlatformCompatGating" + }, + // CTS tests + { + "name": "CtsAppCompatHostTestCases#" + } + ] +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java index b0e2e6432c77..6a5e963064bb 100644 --- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java +++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java @@ -16,9 +16,7 @@ package com.android.server.incremental; -import static android.content.pm.InstallationFile.FILE_TYPE_OBB; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; -import static android.content.pm.PackageInstaller.LOCATION_MEDIA_OBB; import android.annotation.IntDef; import android.annotation.NonNull; @@ -183,10 +181,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { session = packageInstaller.openSession(sessionId); for (int i = 0; i < numFiles; i++) { InstallationFile file = installationFiles.get(i); - final int location = file.getFileType() == FILE_TYPE_OBB ? LOCATION_MEDIA_OBB - : LOCATION_DATA_APP; - session.addFile(location, file.getName(), file.getSize(), file.getMetadata(), - null); + session.addFile(file.getLocation(), file.getName(), file.getLengthBytes(), + file.getMetadata(), file.getSignature()); } session.commit(localReceiver.getIntentSender()); final Intent result = localReceiver.getResult(); @@ -304,7 +300,8 @@ public final class IncrementalManagerShellCommand extends ShellCommand { } final byte[] metadata = String.valueOf(index).getBytes( StandardCharsets.UTF_8); - fileList.add(new InstallationFile(name, size, metadata)); + fileList.add( + new InstallationFile(LOCATION_DATA_APP, name, size, metadata, null)); break; } default: diff --git a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java index c3899638f40f..94e6708c3038 100644 --- a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java +++ b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java @@ -29,13 +29,14 @@ public final class ComponentBitSize { public static final int KEY_BITS = 4; public static final int OPERATOR_BITS = 3; public static final int CONNECTOR_BITS = 2; - public static final int SEPARATOR_BITS = 2; + public static final int SEPARATOR_BITS = 3; public static final int VALUE_SIZE_BITS = 8; public static final int IS_HASHED_BITS = 1; public static final int ATOMIC_FORMULA_START = 0; public static final int COMPOUND_FORMULA_START = 1; public static final int COMPOUND_FORMULA_END = 2; + public static final int INSTALLER_ALLOWED_BY_MANIFEST_START = 3; public static final int DEFAULT_FORMAT_VERSION = 1; public static final int SIGNAL_BIT = 1; diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java index 4b8efafcb6b0..11e8d91dde12 100644 --- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java +++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java @@ -23,6 +23,7 @@ import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMU import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; +import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START; import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS; import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; @@ -35,6 +36,7 @@ import static com.android.server.integrity.parser.BinaryFileOperations.getString import android.content.integrity.AtomicFormula; import android.content.integrity.CompoundFormula; +import android.content.integrity.InstallerAllowedByManifestFormula; import android.content.integrity.IntegrityFormula; import android.content.integrity.Rule; @@ -140,6 +142,8 @@ public class RuleBinaryParser implements RuleParser { return parseCompoundFormula(bitInputStream); case COMPOUND_FORMULA_END: return null; + case INSTALLER_ALLOWED_BY_MANIFEST_START: + return new InstallerAllowedByManifestFormula(); default: throw new IllegalArgumentException( String.format("Unknown formula separator: %s", separator)); diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java index 00e054596cd7..8ba5870aef0f 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java @@ -23,6 +23,7 @@ import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION; import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS; import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS; +import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START; import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS; import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS; import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS; @@ -36,6 +37,7 @@ import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAG import android.content.integrity.AtomicFormula; import android.content.integrity.CompoundFormula; +import android.content.integrity.InstallerAllowedByManifestFormula; import android.content.integrity.IntegrityFormula; import android.content.integrity.IntegrityUtils; import android.content.integrity.Rule; @@ -202,6 +204,8 @@ public class RuleBinarySerializer implements RuleSerializer { serializeAtomicFormula((AtomicFormula) formula, bitOutputStream); } else if (formula instanceof CompoundFormula) { serializeCompoundFormula((CompoundFormula) formula, bitOutputStream); + } else if (formula instanceof InstallerAllowedByManifestFormula) { + bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START); } else { throw new IllegalArgumentException( String.format("Invalid formula type: %s", formula.getClass())); diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java index 6f7d172aabcc..e7235591fb9b 100644 --- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java +++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java @@ -84,6 +84,7 @@ class RuleIndexingDetailsIdentifier { return getIndexingDetailsForStringAtomicFormula( (AtomicFormula.StringAtomicFormula) formula); case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG: + case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG: case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG: // Package name and app certificate related formulas are string atomic formulas. return new RuleIndexingDetails(NOT_INDEXED); diff --git a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java index 55e427f70180..6ba5f079264c 100644 --- a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java @@ -18,6 +18,7 @@ package com.android.server.location; import android.content.Context; import android.location.GnssMeasurementsEvent; +import android.location.GnssRequest; import android.location.IGnssMeasurementsListener; import android.os.Handler; import android.os.RemoteException; @@ -33,14 +34,14 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ public abstract class GnssMeasurementsProvider - extends RemoteListenerHelper<IGnssMeasurementsListener> { - private static final String TAG = "GnssMeasurementsProvider"; + extends RemoteListenerHelper<GnssRequest, IGnssMeasurementsListener> { + private static final String TAG = "GnssMeasProvider"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final GnssMeasurementProviderNative mNative; - private boolean mIsCollectionStarted; - private boolean mEnableFullTracking; + private boolean mStartedCollection; + private boolean mStartedFullTracking; protected GnssMeasurementsProvider(Context context, Handler handler) { this(context, handler, new GnssMeasurementProviderNative()); @@ -57,8 +58,8 @@ public abstract class GnssMeasurementsProvider if (DEBUG) { Log.d(TAG, "resumeIfStarted"); } - if (mIsCollectionStarted) { - mNative.startMeasurementCollection(mEnableFullTracking); + if (mStartedCollection) { + mNative.startMeasurementCollection(mStartedFullTracking); } } @@ -67,18 +68,35 @@ public abstract class GnssMeasurementsProvider return mNative.isMeasurementSupported(); } - @Override - protected int registerWithService() { + private boolean getMergedFullTracking() { int devOptions = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0); - int fullTrackingToggled = Settings.Global.getInt(mContext.getContentResolver(), + int enableFullTracking = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING, 0); - boolean enableFullTracking = (devOptions == 1 /* Developer Mode enabled */) - && (fullTrackingToggled == 1 /* Raw Measurements Full Tracking enabled */); + boolean enableFullTrackingBySetting = (devOptions == 1 /* Developer Mode enabled */) + && (enableFullTracking == 1 /* Raw Measurements Full Tracking enabled */); + if (enableFullTrackingBySetting) { + return true; + } + + synchronized (mListenerMap) { + for (IdentifiedListener identifiedListener : mListenerMap.values()) { + GnssRequest request = identifiedListener.getRequest(); + if (request != null && request.isFullTracking()) { + return true; + } + } + } + return false; + } + + @Override + protected int registerWithService() { + boolean enableFullTracking = getMergedFullTracking(); boolean result = mNative.startMeasurementCollection(enableFullTracking); if (result) { - mIsCollectionStarted = true; - mEnableFullTracking = enableFullTracking; + mStartedCollection = true; + mStartedFullTracking = enableFullTracking; return RemoteListenerHelper.RESULT_SUCCESS; } else { return RemoteListenerHelper.RESULT_INTERNAL_ERROR; @@ -89,7 +107,7 @@ public abstract class GnssMeasurementsProvider protected void unregisterFromService() { boolean stopped = mNative.stopMeasurementCollection(); if (stopped) { - mIsCollectionStarted = false; + mStartedCollection = false; } } diff --git a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java index 983d1daa2367..fb901e86f494 100644 --- a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java +++ b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java @@ -33,7 +33,7 @@ import com.android.internal.annotations.VisibleForTesting; * @hide */ public abstract class GnssNavigationMessageProvider - extends RemoteListenerHelper<IGnssNavigationMessageListener> { + extends RemoteListenerHelper<Void, IGnssNavigationMessageListener> { private static final String TAG = "GnssNavigationMessageProvider"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); diff --git a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java index eaf63c87d93f..1d16c03fd6f7 100644 --- a/services/core/java/com/android/server/location/GnssStatusListenerHelper.java +++ b/services/core/java/com/android/server/location/GnssStatusListenerHelper.java @@ -24,7 +24,8 @@ import android.util.Log; /** * Implementation of a handler for {@link IGnssStatusListener}. */ -public abstract class GnssStatusListenerHelper extends RemoteListenerHelper<IGnssStatusListener> { +public abstract class GnssStatusListenerHelper extends + RemoteListenerHelper<Void, IGnssStatusListener> { private static final String TAG = "GnssStatusListenerHelper"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java index 015227394ffe..11f068533a6d 100644 --- a/services/core/java/com/android/server/location/RemoteListenerHelper.java +++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java @@ -17,6 +17,7 @@ package com.android.server.location; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.AppOpsManager; import android.content.Context; import android.os.Handler; @@ -32,9 +33,10 @@ import java.util.Objects; /** * A helper class that handles operations in remote listeners. * + * @param <TRequest> the type of request. * @param <TListener> the type of GNSS data listener. */ -public abstract class RemoteListenerHelper<TListener extends IInterface> { +public abstract class RemoteListenerHelper<TRequest, TListener extends IInterface> { protected static final int RESULT_SUCCESS = 0; protected static final int RESULT_NOT_AVAILABLE = 1; @@ -47,7 +49,7 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> { protected final Handler mHandler; private final String mTag; - private final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>(); + protected final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>(); protected final Context mContext; protected final AppOpsManager mAppOps; @@ -75,7 +77,8 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> { /** * Adds GNSS data listener {@code listener} with caller identify {@code callerIdentify}. */ - public void addListener(@NonNull TListener listener, CallerIdentity callerIdentity) { + public void addListener(@Nullable TRequest request, @NonNull TListener listener, + CallerIdentity callerIdentity) { Objects.requireNonNull(listener, "Attempted to register a 'null' listener."); IBinder binder = listener.asBinder(); synchronized (mListenerMap) { @@ -84,7 +87,7 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> { return; } - IdentifiedListener identifiedListener = new IdentifiedListener(listener, + IdentifiedListener identifiedListener = new IdentifiedListener(request, listener, callerIdentity); mListenerMap.put(binder, identifiedListener); @@ -257,14 +260,22 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> { return RESULT_SUCCESS; } - private class IdentifiedListener { + protected class IdentifiedListener { + @Nullable private final TRequest mRequest; private final TListener mListener; private final CallerIdentity mCallerIdentity; - private IdentifiedListener(@NonNull TListener listener, CallerIdentity callerIdentity) { + private IdentifiedListener(@Nullable TRequest request, @NonNull TListener listener, + CallerIdentity callerIdentity) { mListener = listener; + mRequest = request; mCallerIdentity = callerIdentity; } + + @Nullable + protected TRequest getRequest() { + return mRequest; + } } private class HandlerRunnable implements Runnable { diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java index 9163490fa777..6d1d1f901eb3 100644 --- a/services/core/java/com/android/server/location/SettingsHelper.java +++ b/services/core/java/com/android/server/location/SettingsHelper.java @@ -135,6 +135,24 @@ public class SettingsHelper { } /** + * Set location enabled for a user. + */ + public void setLocationEnabled(boolean enabled, int userId) { + long identity = Binder.clearCallingIdentity(); + try { + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.LOCATION_MODE, + enabled + ? Settings.Secure.LOCATION_MODE_ON + : Settings.Secure.LOCATION_MODE_OFF, + userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** * Add a listener for changes to the location enabled setting. Callbacks occur on an unspecified * thread. */ diff --git a/services/core/java/com/android/server/location/TEST_MAPPING b/services/core/java/com/android/server/location/TEST_MAPPING index 2e21fa67a967..214d2f3f5646 100644 --- a/services/core/java/com/android/server/location/TEST_MAPPING +++ b/services/core/java/com/android/server/location/TEST_MAPPING @@ -1,10 +1,19 @@ { "presubmit": [ { + "name": "CtsLocationFineTestCases" + }, + { "name": "CtsLocationCoarseTestCases" }, { "name": "CtsLocationNoneTestCases" + }, + { + "name": "FrameworksMockingServicesTests", + "options": [{ + "include-filter": "com.android.server.location" + }] } ] }
\ No newline at end of file diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index 2bab9fa67eb0..02c1bddd8c00 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -25,6 +25,7 @@ import android.app.AppOpsManager; import android.content.Context; import android.location.GnssCapabilities; import android.location.GnssMeasurementCorrections; +import android.location.GnssRequest; import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; import android.location.IGnssNavigationMessageListener; @@ -96,15 +97,15 @@ public class GnssManagerService { private final IGpsGeofenceHardware mGpsGeofenceProxy; @GuardedBy("mGnssMeasurementsListeners") - private final ArrayMap<IBinder, LinkedListener<IGnssMeasurementsListener>> + private final ArrayMap<IBinder, LinkedListener<GnssRequest, IGnssMeasurementsListener>> mGnssMeasurementsListeners = new ArrayMap<>(); @GuardedBy("mGnssNavigationMessageListeners") - private final ArrayMap<IBinder, LinkedListener<IGnssNavigationMessageListener>> + private final ArrayMap<IBinder, LinkedListener<Void, IGnssNavigationMessageListener>> mGnssNavigationMessageListeners = new ArrayMap<>(); @GuardedBy("mGnssStatusListeners") - private final ArrayMap<IBinder, LinkedListener<IGnssStatusListener>> + private final ArrayMap<IBinder, LinkedListener<Void, IGnssStatusListener>> mGnssStatusListeners = new ArrayMap<>(); @GuardedBy("this") @@ -118,7 +119,8 @@ public class GnssManagerService { @Nullable private IBatchedLocationCallback mGnssBatchingCallback; @GuardedBy("mGnssBatchingLock") - @Nullable private LinkedListener<IBatchedLocationCallback> mGnssBatchingDeathCallback; + @Nullable + private LinkedListener<Void, IBatchedLocationCallback> mGnssBatchingDeathCallback; @GuardedBy("mGnssBatchingLock") private boolean mGnssBatchingInProgress = false; @@ -272,6 +274,7 @@ public class GnssManagerService { mGnssBatchingCallback = callback; mGnssBatchingDeathCallback = new LinkedListener<>( + /* request= */ null, callback, "BatchedLocationCallback", callerIdentity, @@ -356,36 +359,39 @@ public class GnssManagerService { } } - private <TListener extends IInterface> void updateListenersOnForegroundChangedLocked( - Map<IBinder, ? extends LinkedListenerBase> gnssDataListeners, - RemoteListenerHelper<TListener> gnssDataProvider, + private <TRequest, TListener extends IInterface> void updateListenersOnForegroundChangedLocked( + Map<IBinder, LinkedListener<TRequest, TListener>> gnssDataListeners, + RemoteListenerHelper<TRequest, TListener> gnssDataProvider, Function<IBinder, TListener> mapBinderToListener, int uid, boolean foreground) { - for (Map.Entry<IBinder, ? extends LinkedListenerBase> entry : + for (Map.Entry<IBinder, LinkedListener<TRequest, TListener>> entry : gnssDataListeners.entrySet()) { - LinkedListenerBase linkedListener = entry.getValue(); + LinkedListener<TRequest, TListener> linkedListener = entry.getValue(); CallerIdentity callerIdentity = linkedListener.getCallerIdentity(); + TRequest request = linkedListener.getRequest(); if (callerIdentity.mUid != uid) { continue; } TListener listener = mapBinderToListener.apply(entry.getKey()); if (foreground || isThrottlingExempt(callerIdentity)) { - gnssDataProvider.addListener(listener, callerIdentity); + gnssDataProvider.addListener(request, listener, callerIdentity); } else { gnssDataProvider.removeListener(listener); } } } - private <TListener extends IInterface> boolean addGnssDataListenerLocked( + private <TListener extends IInterface, TRequest> boolean addGnssDataListenerLocked( + @Nullable TRequest request, TListener listener, String packageName, @Nullable String featureId, @NonNull String listenerIdentifier, - RemoteListenerHelper<TListener> gnssDataProvider, - ArrayMap<IBinder, LinkedListener<TListener>> gnssDataListeners, + RemoteListenerHelper<TRequest, TListener> gnssDataProvider, + ArrayMap<IBinder, + LinkedListener<TRequest, TListener>> gnssDataListeners, Consumer<TListener> binderDeathCallback) { mContext.enforceCallingPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); @@ -395,7 +401,7 @@ public class GnssManagerService { CallerIdentity callerIdentity = new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName, featureId, listenerIdentifier); - LinkedListener<TListener> linkedListener = new LinkedListener<>(listener, + LinkedListener<TRequest, TListener> linkedListener = new LinkedListener<>(request, listener, listenerIdentifier, callerIdentity, binderDeathCallback); IBinder binder = listener.asBinder(); if (!linkedListener.linkToListenerDeathNotificationLocked(binder)) { @@ -419,15 +425,15 @@ public class GnssManagerService { } if (mAppForegroundHelper.isAppForeground(callerIdentity.mUid) || isThrottlingExempt(callerIdentity)) { - gnssDataProvider.addListener(listener, callerIdentity); + gnssDataProvider.addListener(request, listener, callerIdentity); } return true; } - private <TListener extends IInterface> void removeGnssDataListenerLocked( + private <TRequest, TListener extends IInterface> void removeGnssDataListenerLocked( TListener listener, - RemoteListenerHelper<TListener> gnssDataProvider, - ArrayMap<IBinder, LinkedListener<TListener>> gnssDataListeners) { + RemoteListenerHelper<TRequest, TListener> gnssDataProvider, + ArrayMap<IBinder, LinkedListener<TRequest, TListener>> gnssDataListeners) { if (gnssDataProvider == null) { Log.e( TAG, @@ -437,7 +443,7 @@ public class GnssManagerService { } IBinder binder = listener.asBinder(); - LinkedListener<TListener> linkedListener = + LinkedListener<TRequest, TListener> linkedListener = gnssDataListeners.remove(binder); if (linkedListener == null) { return; @@ -467,6 +473,7 @@ public class GnssManagerService { @Nullable String featureId) { synchronized (mGnssStatusListeners) { return addGnssDataListenerLocked( + /* request= */ null, listener, packageName, featureId, @@ -489,11 +496,17 @@ public class GnssManagerService { /** * Adds a GNSS measurements listener. */ - public boolean addGnssMeasurementsListener( - IGnssMeasurementsListener listener, String packageName, @Nullable String featureId, + public boolean addGnssMeasurementsListener(@Nullable GnssRequest request, + IGnssMeasurementsListener listener, String packageName, + @Nullable String featureId, @NonNull String listenerIdentifier) { + if (request != null && request.isFullTracking()) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.LOCATION_HARDWARE, + null); + } synchronized (mGnssMeasurementsListeners) { return addGnssDataListenerLocked( + request, listener, packageName, featureId, @@ -538,6 +551,7 @@ public class GnssManagerService { @Nullable String featureId, @NonNull String listenerIdentifier) { synchronized (mGnssNavigationMessageListeners) { return addGnssDataListenerLocked( + /* request= */ null, listener, packageName, featureId, diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 0cd1c259dd9e..e9f84fd419d6 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import android.Manifest.permission; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; @@ -305,6 +306,10 @@ public class LauncherAppsService extends SystemService { final int callingUserId = injectCallingUserId(); if (targetUserId == callingUserId) return true; + if (mContext.checkCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL) + == PackageManager.PERMISSION_GRANTED) { + return true; + } long ident = injectClearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 944280d6db88..97defcdd3bc7 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -214,7 +214,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill"; private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT; - private static final FileInfo[] EMPTY_FILE_INFO_ARRAY = {}; + private static final InstallationFile[] EMPTY_INSTALLATION_FILE_ARRAY = {}; private static final String SYSTEM_DATA_LOADER_PACKAGE = "android"; @@ -309,33 +309,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private int mParentSessionId; - static class FileInfo { - public final int location; - public final String name; - public final Long lengthBytes; - public final byte[] metadata; - public final byte[] signature; - - public static FileInfo added(int location, String name, Long lengthBytes, byte[] metadata, - byte[] signature) { - return new FileInfo(location, name, lengthBytes, metadata, signature); - } - - public static FileInfo removed(int location, String name) { - return new FileInfo(location, name, -1L, null, null); - } - - FileInfo(int location, String name, Long lengthBytes, byte[] metadata, byte[] signature) { - this.location = location; - this.name = name; - this.lengthBytes = lengthBytes; - this.metadata = metadata; - this.signature = signature; - } - } - @GuardedBy("mLock") - private ArrayList<FileInfo> mFiles = new ArrayList<>(); + private ArrayList<InstallationFile> mFiles = new ArrayList<>(); @GuardedBy("mLock") private boolean mStagedSessionApplied; @@ -508,7 +483,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager, int sessionId, int userId, int installerUid, @NonNull InstallSource installSource, SessionParams params, long createdMillis, - File stageDir, String stageCid, FileInfo[] files, boolean prepared, + File stageDir, String stageCid, InstallationFile[] files, boolean prepared, boolean committed, boolean sealed, @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, boolean isFailed, boolean isApplied, int stagedSessionErrorCode, @@ -539,7 +514,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { this.mParentSessionId = parentSessionId; if (files != null) { - for (FileInfo file : files) { + for (InstallationFile file : files) { mFiles.add(file); } } @@ -738,7 +713,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { String[] result = new String[mFiles.size()]; for (int i = 0, size = mFiles.size(); i < size; ++i) { - result[i] = mFiles.get(i).name; + result[i] = mFiles.get(i).getName(); } return result; } @@ -2425,7 +2400,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("addFile"); - mFiles.add(FileInfo.added(location, name, lengthBytes, metadata, signature)); + mFiles.add(new InstallationFile(location, name, lengthBytes, metadata, signature)); } } @@ -2443,7 +2418,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertCallerIsOwnerOrRootLocked(); assertPreparedAndNotSealedLocked("removeFile"); - mFiles.add(FileInfo.removed(location, getRemoveMarkerName(name))); + mFiles.add(new InstallationFile(location, getRemoveMarkerName(name), -1, null, null)); } } @@ -2461,17 +2436,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } final List<InstallationFile> addedFiles = new ArrayList<>(mFiles.size()); - for (FileInfo file : mFiles) { - if (sAddedFilter.accept(new File(this.stageDir, file.name))) { - addedFiles.add(new InstallationFile( - file.name, file.lengthBytes, file.metadata)); + for (InstallationFile file : mFiles) { + if (sAddedFilter.accept(new File(this.stageDir, file.getName()))) { + addedFiles.add(file); } } final List<String> removedFiles = new ArrayList<>(mFiles.size()); - for (FileInfo file : mFiles) { - if (sRemovedFilter.accept(new File(this.stageDir, file.name))) { - String name = file.name.substring( - 0, file.name.length() - REMOVE_MARKER_EXTENSION.length()); + for (InstallationFile file : mFiles) { + if (sRemovedFilter.accept(new File(this.stageDir, file.getName()))) { + String name = file.getName().substring( + 0, file.getName().length() - REMOVE_MARKER_EXTENSION.length()); removedFiles.add(name); } } @@ -2970,13 +2944,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeIntAttribute(out, ATTR_SESSION_ID, childSessionId); out.endTag(null, TAG_CHILD_SESSION); } - for (FileInfo fileInfo : mFiles) { + for (InstallationFile file : mFiles) { out.startTag(null, TAG_SESSION_FILE); - writeIntAttribute(out, ATTR_LOCATION, fileInfo.location); - writeStringAttribute(out, ATTR_NAME, fileInfo.name); - writeLongAttribute(out, ATTR_LENGTH_BYTES, fileInfo.lengthBytes); - writeByteArrayAttribute(out, ATTR_METADATA, fileInfo.metadata); - writeByteArrayAttribute(out, ATTR_SIGNATURE, fileInfo.signature); + writeIntAttribute(out, ATTR_LOCATION, file.getLocation()); + writeStringAttribute(out, ATTR_NAME, file.getName()); + writeLongAttribute(out, ATTR_LENGTH_BYTES, file.getLengthBytes()); + writeByteArrayAttribute(out, ATTR_METADATA, file.getMetadata()); + writeByteArrayAttribute(out, ATTR_SIGNATURE, file.getSignature()); out.endTag(null, TAG_SESSION_FILE); } } @@ -3088,7 +3062,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { List<String> grantedRuntimePermissions = new ArrayList<>(); List<String> whitelistedRestrictedPermissions = new ArrayList<>(); List<Integer> childSessionIds = new ArrayList<>(); - List<FileInfo> files = new ArrayList<>(); + List<InstallationFile> files = new ArrayList<>(); int outerDepth = in.getDepth(); int type; while ((type = in.next()) != XmlPullParser.END_DOCUMENT @@ -3107,7 +3081,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID)); } if (TAG_SESSION_FILE.equals(in.getName())) { - files.add(new FileInfo( + files.add(new InstallationFile( readIntAttribute(in, ATTR_LOCATION, 0), readStringAttribute(in, ATTR_NAME), readLongAttribute(in, ATTR_LENGTH_BYTES, -1), @@ -3135,16 +3109,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY; } - FileInfo[] fileInfosArray = null; + InstallationFile[] fileArray = null; if (!files.isEmpty()) { - fileInfosArray = files.toArray(EMPTY_FILE_INFO_ARRAY); + fileArray = files.toArray(EMPTY_INSTALLATION_FILE_ARRAY); } InstallSource installSource = InstallSource.create(installInitiatingPackageName, installOriginatingPackageName, installerPackageName); return new PackageInstallerSession(callback, context, pm, sessionProvider, installerThread, stagingManager, sessionId, userId, installerUid, - installSource, params, createdMillis, stageDir, stageCid, fileInfosArray, + installSource, params, createdMillis, stageDir, stageCid, fileArray, prepared, committed, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage); } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index cb9404397f3d..c69a62d406aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -1163,7 +1163,7 @@ class PackageManagerShellCommand extends ShellCommand { final InstallParams params = makeInstallParams(); if (params.sessionParams.dataLoaderParams == null) { params.sessionParams.setDataLoaderParams( - PackageManagerShellCommandDataLoader.getDataLoaderParams(this)); + PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(this)); } return doRunInstall(params); } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java index 5dca9e147e31..4170be4a913d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java @@ -54,7 +54,7 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { private static final String STDIN_PATH = "-"; - static DataLoaderParams getDataLoaderParams(ShellCommand shellCommand) { + private static String getDataLoaderParamsArgs(ShellCommand shellCommand) { int commandId; synchronized (sShellCommands) { // Clean up old references. @@ -78,8 +78,12 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { sShellCommands.put(commandId, new WeakReference<>(shellCommand)); } - final String args = SHELL_COMMAND_ID_PREFIX + commandId; - return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), args); + return SHELL_COMMAND_ID_PREFIX + commandId; + } + + static DataLoaderParams getStreamingDataLoaderParams(ShellCommand shellCommand) { + return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), + getDataLoaderParamsArgs(shellCommand)); } private static int extractShellCommandId(String args) { @@ -133,17 +137,17 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { return false; } try { - for (InstallationFile fileInfo : addedFiles) { - String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8); + for (InstallationFile file : addedFiles) { + String filePath = new String(file.getMetadata(), StandardCharsets.UTF_8); if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) { final ParcelFileDescriptor inFd = ParcelFileDescriptor.dup( shellCommand.getInFileDescriptor()); - mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd); + mConnector.writeData(file.getName(), 0, file.getLengthBytes(), inFd); } else { ParcelFileDescriptor incomingFd = null; try { incomingFd = shellCommand.openFileForSystem(filePath, "r"); - mConnector.writeData(fileInfo.getName(), 0, incomingFd.getStatSize(), + mConnector.writeData(file.getName(), 0, incomingFd.getStatSize(), incomingFd); } finally { IoUtils.closeQuietly(incomingFd); @@ -159,7 +163,8 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService { } @Override - public DataLoaderService.DataLoader onCreateDataLoader() { + public DataLoaderService.DataLoader onCreateDataLoader( + @NonNull DataLoaderParams dataLoaderParams) { return new DataLoader(); } } diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java index 77bb48eadc41..4c40448085ff 100644 --- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java +++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java @@ -89,7 +89,7 @@ class UserSystemPackageInstaller { * <ul> * <li> 0 - disable whitelist (install all system packages; no logging)</li> * <li> 1 - enforce (only install system packages if they are whitelisted)</li> - * <li> 2 - log (log when a non-whitelisted package is run)</li> + * <li> 2 - log (log non-whitelisted packages)</li> * <li> 4 - for all users: implicitly whitelist any package not mentioned in the whitelist</li> * <li> 8 - for SYSTEM: implicitly whitelist any package not mentioned in the whitelist</li> * <li> 16 - ignore OTAs (don't install system packages during OTAs)</li> diff --git a/services/core/java/com/android/server/power/PreRebootLogger.java b/services/core/java/com/android/server/power/PreRebootLogger.java new file mode 100644 index 000000000000..cda00b404c0b --- /dev/null +++ b/services/core/java/com/android/server/power/PreRebootLogger.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Environment; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; + +/** + * Provides utils to dump/wipe pre-reboot information. + */ +final class PreRebootLogger { + private static final String TAG = "PreRebootLogger"; + private static final String PREREBOOT_DIR = "prereboot"; + + private static final String[] BUFFERS_TO_DUMP = {"system"}; + private static final String[] SERVICES_TO_DUMP = {Context.ROLLBACK_SERVICE, "package"}; + + private static final Object sLock = new Object(); + + /** + * Process pre-reboot information. Dump pre-reboot information to {@link #PREREBOOT_DIR} if + * enabled {@link Settings.Global#ADB_ENABLED}; wipe dumped information otherwise. + */ + static void log(Context context) { + log(context, getDumpDir()); + } + + @VisibleForTesting + static void log(Context context, @NonNull File dumpDir) { + if (Settings.Global.getInt( + context.getContentResolver(), Settings.Global.ADB_ENABLED, 0) == 1) { + Slog.d(TAG, "Dumping pre-reboot information..."); + dump(dumpDir); + } else { + Slog.d(TAG, "Wiping pre-reboot information..."); + wipe(dumpDir); + } + } + + private static void dump(@NonNull File dumpDir) { + synchronized (sLock) { + for (String buffer : BUFFERS_TO_DUMP) { + dumpLogsLocked(dumpDir, buffer); + } + for (String service : SERVICES_TO_DUMP) { + dumpServiceLocked(dumpDir, service); + } + } + } + + private static void wipe(@NonNull File dumpDir) { + synchronized (sLock) { + for (File file : dumpDir.listFiles()) { + file.delete(); + } + } + } + + private static File getDumpDir() { + final File dumpDir = new File(Environment.getDataMiscDirectory(), PREREBOOT_DIR); + if (!dumpDir.exists() || !dumpDir.isDirectory()) { + throw new UnsupportedOperationException("Pre-reboot dump directory not found"); + } + return dumpDir; + } + + @GuardedBy("sLock") + private static void dumpLogsLocked(@NonNull File dumpDir, @NonNull String buffer) { + try { + final File dumpFile = new File(dumpDir, buffer); + if (dumpFile.createNewFile()) { + dumpFile.setWritable(true /* writable */, true /* ownerOnly */); + } else { + // Wipes dumped information in existing file before recording new information. + new FileWriter(dumpFile, false).flush(); + } + + final String[] cmdline = + {"logcat", "-d", "-b", buffer, "-f", dumpFile.getAbsolutePath()}; + Runtime.getRuntime().exec(cmdline).waitFor(); + } catch (IOException | InterruptedException e) { + Slog.d(TAG, "Dump system log buffer before reboot fail", e); + } + } + + @GuardedBy("sLock") + private static void dumpServiceLocked(@NonNull File dumpDir, @NonNull String serviceName) { + final IBinder binder = ServiceManager.checkService(serviceName); + if (binder == null) { + return; + } + + try { + final File dumpFile = new File(dumpDir, serviceName); + final ParcelFileDescriptor fd = ParcelFileDescriptor.open(dumpFile, + ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE + | ParcelFileDescriptor.MODE_WRITE_ONLY); + binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class)); + } catch (FileNotFoundException | RemoteException e) { + Slog.d(TAG, String.format("Dump %s service before reboot fail", serviceName), e); + } + } +} diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index cc1cddd3a111..bc722f181650 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -44,6 +44,7 @@ import android.os.Vibrator; import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.Log; +import android.util.Slog; import android.util.TimingsTraceLog; import android.view.WindowManager; @@ -446,6 +447,15 @@ public final class ShutdownThread extends Thread { SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1"); } + shutdownTimingLog.traceBegin("DumpPreRebootInfo"); + try { + Slog.i(TAG, "Logging pre-reboot information..."); + PreRebootLogger.log(mContext); + } catch (Exception e) { + Slog.e(TAG, "Failed to log pre-reboot information", e); + } + shutdownTimingLog.traceEnd(); // DumpPreRebootInfo + metricStarted(METRIC_SEND_BROADCAST); shutdownTimingLog.traceBegin("SendShutdownBroadcast"); Log.i(TAG, "Sending shutdown broadcast..."); diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java index 5abd9f0698d3..7b96777b5987 100644 --- a/services/core/java/com/android/server/rollback/Rollback.java +++ b/services/core/java/com/android/server/rollback/Rollback.java @@ -181,15 +181,6 @@ class Rollback { private int mNumPackageSessionsWithSuccess; /** - * A temp flag to facilitate merging of the 2 rollback collections managed by - * RollbackManagerServiceImpl. True if this rollback is in the process of enabling and was - * originally managed by RollbackManagerServiceImpl#mNewRollbacks. - * TODO: remove this flag when merge is completed. - */ - @GuardedBy("mLock") - private boolean mIsNewRollback = false; - - /** * Constructs a new, empty Rollback instance. * * @param rollbackId the id of the rollback. @@ -837,18 +828,6 @@ class Rollback { } } - void setIsNewRollback(boolean newRollback) { - synchronized (mLock) { - mIsNewRollback = newRollback; - } - } - - boolean isNewRollback() { - synchronized (mLock) { - return mIsNewRollback; - } - } - static String rollbackStateToString(@RollbackState int state) { switch (state) { case Rollback.ROLLBACK_STATE_ENABLING: return "enabling"; diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 1421258c12f6..91e7cc981b89 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -164,8 +164,16 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // Load rollback data from device storage. synchronized (mLock) { mRollbacks = mRollbackStore.loadRollbacks(); - for (Rollback rollback : mRollbacks) { - mAllocatedRollbackIds.put(rollback.info.getRollbackId(), true); + if (!context.getPackageManager().isDeviceUpgrading()) { + for (Rollback rollback : mRollbacks) { + mAllocatedRollbackIds.put(rollback.info.getRollbackId(), true); + } + } else { + // Delete rollbacks when build fingerprint has changed. + for (Rollback rollback : mRollbacks) { + rollback.delete(mAppDataRollbackHelper); + } + mRollbacks.clear(); } } @@ -788,14 +796,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { Rollback newRollback; synchronized (mLock) { - // See if we already have a NewRollback that contains this package - // session. If not, create a NewRollback for the parent session + // See if we already have a Rollback that contains this package + // session. If not, create a new Rollback for the parent session // that we will use for all the packages in the session. - newRollback = getNewRollbackForPackageSessionLocked(packageSession.getSessionId()); + newRollback = getRollbackForSessionLocked(packageSession.getSessionId()); if (newRollback == null) { newRollback = createNewRollbackLocked(parentSession); mRollbacks.add(newRollback); - newRollback.setIsNewRollback(true); } } newRollback.addToken(token); @@ -1148,24 +1155,23 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } if (success) { - Rollback newRollback; + Rollback rollback; synchronized (mLock) { - newRollback = getNewRollbackForPackageSessionLocked(sessionId); - if (newRollback != null && newRollback.notifySessionWithSuccess()) { - mRollbacks.remove(newRollback); - newRollback.setIsNewRollback(false); - } else { - // Not all child sessions finished with success. - // Don't enable the rollback yet. - newRollback = null; + rollback = getRollbackForSessionLocked(sessionId); + if (rollback == null || rollback.isStaged() || !rollback.isEnabling() + || !rollback.notifySessionWithSuccess()) { + return; } + // All child sessions finished with success. We can enable this rollback now. + // TODO: refactor #completeEnableRollback so we won't remove 'rollback' from + // mRollbacks here and add it back in #completeEnableRollback later. + mRollbacks.remove(rollback); } - - if (newRollback != null) { - Rollback rollback = completeEnableRollback(newRollback); - if (rollback != null && !rollback.isStaged()) { - makeRollbackAvailable(rollback); - } + // TODO: Now #completeEnableRollback returns the same rollback object as the + // parameter on success. It would be more readable to return a boolean to indicate + // success or failure. + if (completeEnableRollback(rollback) != null) { + makeRollbackAvailable(rollback); } } else { synchronized (mLock) { @@ -1354,22 +1360,4 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } return null; } - - /** - * Returns the NewRollback associated with the given package session. - * Returns null if no NewRollback is found for the given package - * session. - */ - @WorkerThread - @GuardedBy("mLock") - Rollback getNewRollbackForPackageSessionLocked(int packageSessionId) { - // We expect mRollbacks to be a very small list; linear search - // should be plenty fast. - for (Rollback rollback: mRollbacks) { - if (rollback.isNewRollback() && rollback.containsSessionId(packageSessionId)) { - return rollback; - } - } - return null; - } } diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java index 34d2c16ed0ac..58455ca2753b 100644 --- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -36,7 +36,7 @@ import android.service.textclassifier.ITextClassifierService; import android.service.textclassifier.TextClassifierService; import android.service.textclassifier.TextClassifierService.ConnectionState; import android.text.TextUtils; -import android.util.LruCache; +import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.view.textclassifier.ConversationActions; @@ -65,6 +65,7 @@ import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Queue; @@ -146,11 +147,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi private final Object mLock; @GuardedBy("mLock") final SparseArray<UserState> mUserStates = new SparseArray<>(); - // SystemTextClassifier.onDestroy() is not guaranteed to be called, use LruCache here - // to avoid leak. - @GuardedBy("mLock") - private final LruCache<TextClassificationSessionId, TextClassificationContext> - mSessionContextCache = new LruCache<>(40); + private final SessionCache mSessionCache; private final TextClassificationConstants mSettings; @Nullable private final String mDefaultTextClassifierPackage; @@ -165,6 +162,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi PackageManager packageManager = mContext.getPackageManager(); mDefaultTextClassifierPackage = packageManager.getDefaultTextClassifierPackageName(); mSystemTextClassifierPackage = packageManager.getSystemTextClassifierPackageName(); + mSessionCache = new SessionCache(mLock); } private void startListenSettings() { @@ -314,7 +312,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi classificationContext.getUseDefaultTextClassifier(), service -> { service.onCreateTextClassificationSession(classificationContext, sessionId); - mSessionContextCache.put(sessionId, classificationContext); + mSessionCache.put(sessionId, classificationContext); }, "onCreateTextClassificationSession", NO_OP_CALLBACK); @@ -326,14 +324,14 @@ public final class TextClassificationManagerService extends ITextClassifierServi Objects.requireNonNull(sessionId); synchronized (mLock) { - TextClassificationContext textClassificationContext = - mSessionContextCache.get(sessionId); + final StrippedTextClassificationContext textClassificationContext = + mSessionCache.get(sessionId); final int userId = textClassificationContext != null - ? textClassificationContext.getUserId() + ? textClassificationContext.userId : UserHandle.getCallingUserId(); final boolean useDefaultTextClassifier = textClassificationContext != null - ? textClassificationContext.getUseDefaultTextClassifier() + ? textClassificationContext.useDefaultTextClassifier : true; handleRequest( userId, @@ -342,7 +340,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi useDefaultTextClassifier, service -> { service.onDestroyTextClassificationSession(sessionId); - mSessionContextCache.remove(sessionId); + mSessionCache.remove(sessionId); }, "onDestroyTextClassificationSession", NO_OP_CALLBACK); @@ -409,7 +407,7 @@ public final class TextClassificationManagerService extends ITextClassifierServi pw.decreaseIndent(); } } - pw.println("Number of active sessions: " + mSessionContextCache.size()); + pw.println("Number of active sessions: " + mSessionCache.size()); } } @@ -568,6 +566,81 @@ public final class TextClassificationManagerService extends ITextClassifierServi } } + /** + * Stores the stripped down version of {@link TextClassificationContext}s, i.e. {@link + * StrippedTextClassificationContext}, keyed by {@link TextClassificationSessionId}. Sessions + * are cleaned up automatically when the client process is dead. + */ + static final class SessionCache { + @NonNull + private final Object mLock; + @NonNull + @GuardedBy("mLock") + private final Map<TextClassificationSessionId, StrippedTextClassificationContext> mCache = + new ArrayMap<>(); + @NonNull + @GuardedBy("mLock") + private final Map<TextClassificationSessionId, DeathRecipient> mDeathRecipients = + new ArrayMap<>(); + + SessionCache(@NonNull Object lock) { + mLock = Objects.requireNonNull(lock); + } + + void put(@NonNull TextClassificationSessionId sessionId, + @NonNull TextClassificationContext textClassificationContext) { + synchronized (mLock) { + mCache.put(sessionId, + new StrippedTextClassificationContext(textClassificationContext)); + try { + DeathRecipient deathRecipient = () -> remove(sessionId); + sessionId.getToken().linkToDeath(deathRecipient, /* flags= */ 0); + mDeathRecipients.put(sessionId, deathRecipient); + } catch (RemoteException e) { + Slog.w(LOG_TAG, "SessionCache: Failed to link to death", e); + } + } + } + + @Nullable + StrippedTextClassificationContext get(@NonNull TextClassificationSessionId sessionId) { + Objects.requireNonNull(sessionId); + synchronized (mLock) { + return mCache.get(sessionId); + } + } + + void remove(@NonNull TextClassificationSessionId sessionId) { + Objects.requireNonNull(sessionId); + synchronized (mLock) { + DeathRecipient deathRecipient = mDeathRecipients.get(sessionId); + if (deathRecipient != null) { + sessionId.getToken().unlinkToDeath(deathRecipient, /* flags= */ 0); + } + mDeathRecipients.remove(sessionId); + mCache.remove(sessionId); + } + } + + int size() { + synchronized (mLock) { + return mCache.size(); + } + } + } + + /** A stripped down version of {@link TextClassificationContext}. */ + static class StrippedTextClassificationContext { + @UserIdInt + public final int userId; + public final boolean useDefaultTextClassifier; + + StrippedTextClassificationContext(TextClassificationContext textClassificationContext) { + userId = textClassificationContext.getUserId(); + useDefaultTextClassifier = textClassificationContext.getUseDefaultTextClassifier(); + } + } + private final class UserState { @UserIdInt final int mUserId; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a54f5d43751c..aa6d8545f705 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7674,7 +7674,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mainWindow.getContentInsets(insets); InsetUtils.addInsets(insets, getLetterboxInsets()); return new RemoteAnimationTarget(task.mTaskId, record.getMode(), - record.mAdapter.mCapturedLeash, !task.fillsParent(), + record.mAdapter.mCapturedLeash, !fillsParent(), mainWindow.mWinAnimator.mLastClipRect, insets, getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mStackBounds, task.getWindowConfiguration(), @@ -7692,4 +7692,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } win.getAnimationFrames(outFrame, outInsets, outStableInsets, outSurfaceInsets); } + + void setPictureInPictureParams(PictureInPictureParams p) { + pictureInPictureArgs.copyOnlySet(p); + getTask().getRootTask().setPictureInPictureParams(p); + } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 5f3e3a39490c..4b96ea0a85dd 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2795,6 +2795,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + // TODO(148895075): deprecate and replace with task equivalents @Override public List<ActivityManager.StackInfo> getAllStackInfos() { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()"); @@ -2821,6 +2822,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + // TODO(148895075): deprecate and replace with task equivalents @Override public List<ActivityManager.StackInfo> getAllStackInfosOnDisplay(int displayId) { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()"); @@ -4153,7 +4155,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return; } // Only update the saved args from the args that are set - r.pictureInPictureArgs.copyOnlySet(params); + r.setPictureInPictureParams(params); final float aspectRatio = r.pictureInPictureArgs.getAspectRatio(); final List<RemoteAction> actions = r.pictureInPictureArgs.getActions(); // Adjust the source bounds by the insets for the transition down @@ -4201,7 +4203,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { "setPictureInPictureParams", token, params); // Only update the saved args from the args that are set - r.pictureInPictureArgs.copyOnlySet(params); + r.setPictureInPictureParams(params); if (r.inPinnedWindowingMode()) { // If the activity is already in picture-in-picture, update the pinned stack now // if it is not already expanding to fullscreen. Otherwise, the arguments will diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java index 06e7b48a1f04..9e93e1455f2c 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java @@ -18,6 +18,9 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; +import android.content.res.Resources; +import android.text.TextUtils; + import com.android.server.wm.DisplayContent.TaskContainers; /** @@ -42,7 +45,18 @@ public abstract class DisplayAreaPolicy { */ protected final TaskContainers mTaskContainers; - DisplayAreaPolicy(WindowManagerService wmService, + /** + * Construct a new {@link DisplayAreaPolicy} + * + * @param wmService the window manager service instance + * @param content the display content for which the policy applies + * @param root the root display area under which the policy operates + * @param imeContainer the ime container that the policy must attach + * @param taskContainers the task container that the policy must attach + * + * @see #attachDisplayAreas() + */ + protected DisplayAreaPolicy(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) { mWmService = wmService; @@ -119,5 +133,55 @@ public abstract class DisplayAreaPolicy { throw new IllegalArgumentException("don't know how to sort " + token); } } + + /** Provider for {@link DisplayAreaPolicy.Default platform-default display area policy}. */ + static class Provider implements DisplayAreaPolicy.Provider { + @Override + public DisplayAreaPolicy instantiate(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + TaskContainers taskContainers) { + return new DisplayAreaPolicy.Default(wmService, content, root, imeContainer, + taskContainers); + } + } + } + + /** + * Provider for {@link DisplayAreaPolicy} instances. + * + * By implementing this interface and overriding the + * {@code config_deviceSpecificDisplayAreaPolicyProvider}, a device-specific implementations + * of {@link DisplayAreaPolicy} can be supplied. + */ + public interface Provider { + /** + * Instantiate a new DisplayAreaPolicy. + * + * @see DisplayAreaPolicy#DisplayAreaPolicy + */ + DisplayAreaPolicy instantiate(WindowManagerService wmService, + DisplayContent content, DisplayArea.Root root, + DisplayArea<? extends WindowContainer> imeContainer, + TaskContainers taskContainers); + + /** + * Instantiate the device-specific {@link Provider}. + */ + static Provider fromResources(Resources res) { + String name = res.getString( + com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider); + if (TextUtils.isEmpty(name)) { + return new DisplayAreaPolicy.Default.Provider(); + } + try { + return (Provider) Class.forName(name).newInstance(); + } catch (ReflectiveOperationException | ClassCastException e) { + throw new IllegalStateException("Couldn't instantiate class " + name + + " for config_deviceSpecificDisplayAreaPolicyProvider:" + + " make sure it has a public zero-argument constructor" + + " and implements DisplayAreaPolicy.Provider", e); + } + } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 810aa3438ea0..3b658c04b8c2 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -304,8 +304,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final DisplayArea.Root mRootDisplayArea = new DisplayArea.Root(mWmService); - private final DisplayAreaPolicy mDisplayAreaPolicy = new DisplayAreaPolicy.Default( - mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers); + private final DisplayAreaPolicy mDisplayAreaPolicy; private WindowState mTmpWindow; private WindowState mTmpWindow2; @@ -1027,6 +1026,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo super.addChild(mWindowContainers, null); super.addChild(mOverlayContainers, null); + mDisplayAreaPolicy = mWmService.mDisplayAreaPolicyProvider.instantiate( + mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers); mWindowContainers.addChildren(); // Sets the display content for the children. @@ -5659,7 +5660,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return activityType == ACTIVITY_TYPE_STANDARD && (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM - || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + || windowingMode == WINDOWING_MODE_MULTI_WINDOW); } /** diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index d0179adadbd7..51b9916159fe 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -44,6 +44,7 @@ import android.view.ViewRootImpl; import android.view.WindowInsetsAnimationCallback; import android.view.WindowInsetsAnimationControlListener; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.DisplayThread; /** @@ -107,11 +108,11 @@ class InsetsPolicy { changed = true; } if (changed) { - startAnimation(mShowingTransientTypes, true, () -> { + mPolicy.getStatusBarManagerInternal().showTransient(mDisplayContent.getDisplayId(), + mShowingTransientTypes.toArray()); + updateBarControlTarget(mFocusedWin); + startAnimation(true /* show */, () -> { synchronized (mDisplayContent.mWmService.mGlobalLock) { - mPolicy.getStatusBarManagerInternal().showTransient( - mDisplayContent.getDisplayId(), - mShowingTransientTypes.toArray()); mStateController.notifyInsetsChanged(); } }); @@ -122,7 +123,7 @@ class InsetsPolicy { if (mShowingTransientTypes.size() == 0) { return; } - startAnimation(mShowingTransientTypes, false, () -> { + startAnimation(false /* show */, () -> { synchronized (mDisplayContent.mWmService.mGlobalLock) { mShowingTransientTypes.clear(); mStateController.notifyInsetsChanged(); @@ -268,18 +269,20 @@ class InsetsPolicy { return isDockedStackVisible || isFreeformStackVisible || isResizing; } - private void startAnimation(IntArray internalTypes, boolean show, Runnable callback) { + @VisibleForTesting + void startAnimation(boolean show, Runnable callback) { int typesReady = 0; final SparseArray<InsetsSourceControl> controls = new SparseArray<>(); - updateBarControlTarget(mFocusedWin); - for (int i = internalTypes.size() - 1; i >= 0; i--) { + final IntArray showingTransientTypes = mShowingTransientTypes; + for (int i = showingTransientTypes.size() - 1; i >= 0; i--) { InsetsSourceProvider provider = - mStateController.getSourceProvider(internalTypes.get(i)); - if (provider == null) continue; - InsetsSourceControl control = provider.getControl(provider.getControlTarget()); - if (control == null || control.getLeash() == null) continue; - typesReady |= InsetsState.toPublicType(internalTypes.get(i)); - controls.put(control.getType(), control); + mStateController.getSourceProvider(showingTransientTypes.get(i)); + InsetsSourceControl control = provider.getControl(mTransientControlTarget); + if (control == null || control.getLeash() == null) { + continue; + } + typesReady |= InsetsState.toPublicType(showingTransientTypes.get(i)); + controls.put(control.getType(), new InsetsSourceControl(control)); } controlAnimationUnchecked(typesReady, controls, show, callback); } @@ -335,7 +338,6 @@ class InsetsPolicy { private InsetsPolicyAnimationControlListener mListener; InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) { - super(); mListener = listener; } @@ -353,9 +355,11 @@ class InsetsPolicy { InsetsController.INTERPOLATOR, true, show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN); + SurfaceAnimationThread.getHandler().post( + () -> mListener.onReady(mAnimationControl, typesReady)); } - /** Called on SurfaceAnimationThread lock without global WM lock held. */ + /** Called on SurfaceAnimationThread without global WM lock held. */ @Override public void scheduleApplyChangeInsets() { InsetsState state = getState(); @@ -384,7 +388,7 @@ class InsetsPolicy { return overrideState; } - /** Called on SurfaceAnimationThread lock without global WM lock held. */ + /** Called on SurfaceAnimationThread without global WM lock held. */ @Override public void applySurfaceParams( final SyncRtSurfaceTransactionApplier.SurfaceParams... params) { @@ -396,14 +400,12 @@ class InsetsPolicy { t.apply(); } - /** Called on SurfaceAnimationThread lock without global WM lock held. */ @Override public void startAnimation(InsetsAnimationControlImpl controller, WindowInsetsAnimationControlListener listener, int types, WindowInsetsAnimationCallback.InsetsAnimation animation, WindowInsetsAnimationCallback.AnimationBounds bounds, int layoutDuringAnimation) { - SurfaceAnimationThread.getHandler().post(() -> listener.onReady(controller, types)); } } } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 798665972a33..0d3f6b98f483 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -59,6 +59,7 @@ class InsetsSourceProvider { private final InsetsSourceControl mFakeControl; private @Nullable InsetsSourceControl mControl; private @Nullable InsetsControlTarget mControlTarget; + private @Nullable InsetsControlTarget mPendingControlTarget; private @Nullable InsetsControlTarget mFakeControlTarget; private @Nullable ControlAdapter mAdapter; @@ -140,8 +141,9 @@ class InsetsSourceProvider { mSource.setVisibleFrame(null); } else if (mControllable) { mWin.setControllableInsetProvider(this); - if (mControlTarget != null) { - updateControlForTarget(mControlTarget, true /* force */); + if (mPendingControlTarget != null) { + updateControlForTarget(mPendingControlTarget, true /* force */); + mPendingControlTarget = null; } } } @@ -245,7 +247,7 @@ class InsetsSourceProvider { setWindow(null, null, null); } if (mWin == null) { - mControlTarget = target; + mPendingControlTarget = target; return; } if (target == mControlTarget && !force) { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index caaa4305406c..2d7d3f18c101 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -89,6 +90,12 @@ class InsetsStateController { if (type == ITYPE_NAVIGATION_BAR) { state.removeSource(ITYPE_IME); state.removeSource(ITYPE_STATUS_BAR); + state.removeSource(ITYPE_CAPTION_BAR); + } + + // Status bar doesn't get influenced by caption bar + if (type == ITYPE_STATUS_BAR) { + state.removeSource(ITYPE_CAPTION_BAR); } // IME needs different frames for certain cases (e.g. navigation bar in gesture nav). @@ -212,18 +219,18 @@ class InsetsStateController { /** * Called when the focused window that is able to control the system bars changes. * - * @param topControlling The target that is now able to control the top bar appearance - * and visibility. + * @param statusControlling The target that is now able to control the status bar appearance + * and visibility. * @param navControlling The target that is now able to control the nav bar appearance * and visibility. */ - void onBarControlTargetChanged(@Nullable InsetsControlTarget topControlling, - @Nullable InsetsControlTarget fakeTopControlling, + void onBarControlTargetChanged(@Nullable InsetsControlTarget statusControlling, + @Nullable InsetsControlTarget fakeStatusControlling, @Nullable InsetsControlTarget navControlling, @Nullable InsetsControlTarget fakeNavControlling) { - onControlChanged(ITYPE_STATUS_BAR, topControlling); + onControlChanged(ITYPE_STATUS_BAR, statusControlling); onControlChanged(ITYPE_NAVIGATION_BAR, navControlling); - onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeTopControlling); + onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling); onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling); notifyPendingInsetsControlChanged(); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 302e7596dec4..326335e6df46 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -99,6 +99,7 @@ import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.AppGlobals; +import android.app.PictureInPictureParams; import android.app.TaskInfo; import android.app.WindowConfiguration; import android.content.ComponentName; @@ -461,6 +462,11 @@ class Task extends WindowContainer<WindowContainer> { */ ITaskOrganizer mTaskOrganizer; + /** + * Last Picture-in-Picture params applicable to the task. Updated when the app + * enters Picture-in-Picture or when setPictureInPictureParams is called. + */ + PictureInPictureParams mPictureInPictureParams = new PictureInPictureParams.Builder().build(); /** * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int, @@ -3217,6 +3223,12 @@ class Task extends WindowContainer<WindowContainer> { // order changes. final Task top = getTopMostTask(); info.resizeMode = top != null ? top.mResizeMode : mResizeMode; + + if (mPictureInPictureParams.empty()) { + info.pictureInPictureParams = null; + } else { + info.pictureInPictureParams = mPictureInPictureParams; + } } /** @@ -3947,4 +3959,10 @@ class Task extends WindowContainer<WindowContainer> { void onWindowFocusChanged(boolean hasFocus) { updateShadowsRadius(hasFocus, getPendingTransaction()); } + + void setPictureInPictureParams(PictureInPictureParams p) { + mPictureInPictureParams.copyOnlySet(p); + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + this, true /* force */); + } } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 0a0530c92a16..4b13a0c1f75d 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -43,6 +43,7 @@ import android.view.IWindowContainer; import android.view.SurfaceControl; import android.view.WindowContainerTransaction; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledLambda; @@ -379,7 +380,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub } @Override - public List<RunningTaskInfo> getChildTasks(IWindowContainer parent) { + public List<RunningTaskInfo> getChildTasks(IWindowContainer parent, + @Nullable int[] activityTypes) { enforceStackPermission("getChildTasks()"); final long ident = Binder.clearCallingIdentity(); try { @@ -405,6 +407,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub for (int i = dc.getStackCount() - 1; i >= 0; --i) { final ActivityStack as = dc.getStackAt(i); if (as.getTile() == container) { + if (activityTypes != null + && !ArrayUtils.contains(activityTypes, as.getActivityType())) { + continue; + } final RunningTaskInfo info = new RunningTaskInfo(); as.fillTaskInfo(info); out.add(info); @@ -417,6 +423,40 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub } } + @Override + public List<RunningTaskInfo> getRootTasks(int displayId, @Nullable int[] activityTypes) { + enforceStackPermission("getRootTasks()"); + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final DisplayContent dc = + mService.mRootWindowContainer.getDisplayContent(displayId); + if (dc == null) { + throw new IllegalArgumentException("Display " + displayId + " doesn't exist"); + } + ArrayList<RunningTaskInfo> out = new ArrayList<>(); + for (int i = dc.getStackCount() - 1; i >= 0; --i) { + final ActivityStack task = dc.getStackAt(i); + if (task.getTile() != null) { + // a tile is supposed to look like a parent, so don't include their + // "children" here. They can be accessed via getChildTasks() + continue; + } + if (activityTypes != null + && !ArrayUtils.contains(activityTypes, task.getActivityType())) { + continue; + } + final RunningTaskInfo info = new RunningTaskInfo(); + task.fillTaskInfo(info); + out.add(info); + } + return out; + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private int sanitizeAndApplyChange(WindowContainer container, WindowContainerTransaction.Change change) { if (!(container instanceof Task)) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a370093a3907..d98c18c077b8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -187,6 +187,7 @@ import android.os.SystemService; import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; +import android.provider.DeviceConfig; import android.provider.Settings; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; @@ -309,6 +310,8 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM; + private static final String WM_USE_BLAST_ADAPTER_FLAG = "wm_use_blast_adapter"; + static final int LAYOUT_REPEAT_THRESHOLD = 4; static final boolean PROFILE_ORIENTATION = false; @@ -413,6 +416,8 @@ public class WindowManagerService extends IWindowManager.Stub final WindowTracing mWindowTracing; + final DisplayAreaPolicy.Provider mDisplayAreaPolicyProvider; + final private KeyguardDisableHandler mKeyguardDisableHandler; // TODO: eventually unify all keyguard state in a common place instead of having it spread over // AM's KeyguardController and the policy's KeyguardServiceDelegate. @@ -623,6 +628,9 @@ public class WindowManagerService extends IWindowManager.Stub // The root of the device window hierarchy. RootWindowContainer mRoot; + // Whether the system should use BLAST for ViewRootImpl + final boolean mUseBLAST; + int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; Rect mDockedStackCreateBounds; @@ -1137,6 +1145,10 @@ public class WindowManagerService extends IWindowManager.Stub mAnimator = new WindowAnimator(this); mRoot = new RootWindowContainer(this); + mUseBLAST = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, + WM_USE_BLAST_ADAPTER_FLAG, false); + mWindowPlacerLocked = new WindowSurfacePlacer(this); mTaskSnapshotController = new TaskSnapshotController(this); @@ -1255,6 +1267,10 @@ public class WindowManagerService extends IWindowManager.Stub LocalServices.addService(WindowManagerInternal.class, new LocalService()); mEmbeddedWindowController = new EmbeddedWindowController(mGlobalLock); + + mDisplayAreaPolicyProvider = DisplayAreaPolicy.Provider.fromResources( + mContext.getResources()); + setGlobalShadowSettings(); } @@ -5051,6 +5067,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public boolean useBLAST() { + return mUseBLAST; + } + + @Override public void getInitialDisplaySize(int displayId, Point size) { synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 109f39d9dbcf..cb687c9144c6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -553,6 +553,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private static final long ADMIN_APP_PASSWORD_COMPLEXITY = 123562444L; + /** + * Admin apps targeting Android R+ may not use + * {@link android.app.admin.DevicePolicyManager#setSecureSetting} to change the deprecated + * {@link android.provider.Settings.Secure#LOCATION_MODE} setting. Instead they should use + * {@link android.app.admin.DevicePolicyManager#setLocationEnabled}. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + private static final long USE_SET_LOCATION_ENABLED = 117835097L; + final Context mContext; final Injector mInjector; final IPackageManager mIPackageManager; @@ -11554,13 +11564,22 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setLocationEnabled(ComponentName who, boolean locationEnabled) { - Objects.requireNonNull(who, "ComponentName is null"); - enforceDeviceOwner(who); + enforceDeviceOwner(Objects.requireNonNull(who)); - UserHandle userHandle = mInjector.binderGetCallingUserHandle(); - mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, - userHandle)); + UserHandle user = mInjector.binderGetCallingUserHandle(); + + mInjector.binderWithCleanCallingIdentity(() -> { + boolean wasLocationEnabled = mInjector.getLocationManager().isLocationEnabledForUser( + user); + mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, user); + + // make a best effort to only show the notification if the admin is actually changing + // something. this is subject to race conditions with settings changes, but those are + // unlikely to realistically interfere + if (wasLocationEnabled != locationEnabled) { + showLocationSettingsChangedNotification(user); + } + }); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_SECURE_SETTING) @@ -11571,6 +11590,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } + private void showLocationSettingsChangedNotification(UserHandle user) { + PendingIntent locationSettingsIntent = mInjector.pendingIntentGetActivityAsUser(mContext, 0, + new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), PendingIntent.FLAG_UPDATE_CURRENT, + null, user); + Notification notification = new Notification.Builder(mContext, + SystemNotificationChannels.DEVICE_ADMIN) + .setSmallIcon(R.drawable.ic_info_outline) + .setContentTitle(mContext.getString(R.string.location_changed_notification_title)) + .setContentText(mContext.getString(R.string.location_changed_notification_text)) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setShowWhen(true) + .setContentIntent(locationSettingsIntent) + .setAutoCancel(true) + .build(); + mInjector.getNotificationManager().notify(SystemMessage.NOTE_LOCATION_CHANGED, + notification); + } + @Override public void requestSetLocationProviderAllowed(ComponentName who, String provider, boolean providerAllowed) { @@ -11641,6 +11679,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { throw new SecurityException(String.format( "Permission denial: Profile owners cannot update %1$s", setting)); } + if (setting.equals(Settings.Secure.LOCATION_MODE) + && isSetSecureSettingLocationModeCheckEnabled(who.getPackageName(), + callingUserId)) { + throw new UnsupportedOperationException(Settings.Secure.LOCATION_MODE + " is " + + "deprecated. Please use setLocationEnabled() instead."); + } if (setting.equals(Settings.Secure.INSTALL_NON_MARKET_APPS)) { if (getTargetSdk(who.getPackageName(), callingUserId) >= Build.VERSION_CODES.O) { throw new UnsupportedOperationException(Settings.Secure.INSTALL_NON_MARKET_APPS @@ -11685,6 +11729,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(callingUserId); } mInjector.settingsSecurePutStringForUser(setting, value, callingUserId); + if (setting.equals(Settings.Secure.LOCATION_MODE)) { + showLocationSettingsChangedNotification(UserHandle.of(callingUserId)); + } }); } DevicePolicyEventLogger @@ -11694,6 +11741,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } + private boolean isSetSecureSettingLocationModeCheckEnabled(String packageName, int userId) { + try { + return mIPlatformCompat.isChangeEnabledByPackageName(USE_SET_LOCATION_ENABLED, + packageName, userId); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e); + return getTargetSdk(packageName, userId) > Build.VERSION_CODES.Q; + } + } + @Override public void setMasterVolumeMuted(ComponentName who, boolean on) { Objects.requireNonNull(who, "ComponentName is null"); diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 3b513774b615..8012e74a6b3c 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -1180,7 +1180,7 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ } // Create new lib file without signature info - incfs::NewFileParams libFileParams; + incfs::NewFileParams libFileParams{}; libFileParams.size = uncompressedLen; libFileParams.verification.hashAlgorithm = INCFS_HASH_NONE; // Metadata of the new lib file is its relative path diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index 2444ddecdb80..2e7ced37e91d 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -122,7 +122,6 @@ public: } RawMetadata getMetadata(StorageId storage, FileId node) const; - std::string getSignatureData(StorageId storage, FileId node) const; std::vector<std::string> listFiles(StorageId storage) const; bool startLoading(StorageId storage) const; diff --git a/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java b/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java new file mode 100644 index 000000000000..203e9804bfa3 --- /dev/null +++ b/services/people/java/com/android/server/people/data/AbstractProtoDiskReadWriter.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.people.data; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.text.format.DateUtils; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Base class for reading and writing protobufs on disk from a root directory. Callers should + * ensure that the root directory is unlocked before doing I/O operations using this class. + * + * @param <T> is the data class representation of a protobuf. + */ +abstract class AbstractProtoDiskReadWriter<T> { + + private static final String TAG = AbstractProtoDiskReadWriter.class.getSimpleName(); + private static final long SHUTDOWN_DISK_WRITE_TIMEOUT = 5L * DateUtils.SECOND_IN_MILLIS; + + private final File mRootDir; + private final ScheduledExecutorService mScheduledExecutorService; + private final long mWriteDelayMs; + + @GuardedBy("this") + private ScheduledFuture<?> mScheduledFuture; + + @GuardedBy("this") + private Map<String, T> mScheduledFileDataMap = new ArrayMap<>(); + + /** + * Child class shall provide a {@link ProtoStreamWriter} to facilitate the writing of data as a + * protobuf on disk. + */ + abstract ProtoStreamWriter<T> protoStreamWriter(); + + /** + * Child class shall provide a {@link ProtoStreamReader} to facilitate the reading of protobuf + * data on disk. + */ + abstract ProtoStreamReader<T> protoStreamReader(); + + AbstractProtoDiskReadWriter(@NonNull File rootDir, long writeDelayMs, + @NonNull ScheduledExecutorService scheduledExecutorService) { + mRootDir = rootDir; + mWriteDelayMs = writeDelayMs; + mScheduledExecutorService = scheduledExecutorService; + } + + @WorkerThread + void delete(@NonNull String fileName) { + final File file = getFile(fileName); + if (!file.exists()) { + return; + } + if (!file.delete()) { + Slog.e(TAG, "Failed to delete file: " + file.getPath()); + } + } + + @WorkerThread + void writeTo(@NonNull String fileName, @NonNull T data) { + final File file = getFile(fileName); + final AtomicFile atomicFile = new AtomicFile(file); + + FileOutputStream fileOutputStream = null; + try { + fileOutputStream = atomicFile.startWrite(); + } catch (IOException e) { + Slog.e(TAG, "Failed to write to protobuf file.", e); + return; + } + + try { + final ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); + protoStreamWriter().write(protoOutputStream, data); + protoOutputStream.flush(); + atomicFile.finishWrite(fileOutputStream); + fileOutputStream = null; + } finally { + // When fileInputStream is null (successful write), this will no-op. + atomicFile.failWrite(fileOutputStream); + } + } + + @WorkerThread + @Nullable + T read(@NonNull String fileName) { + File[] files = mRootDir.listFiles( + pathname -> pathname.isFile() && pathname.getName().equals(fileName)); + if (files == null || files.length == 0) { + return null; + } else if (files.length > 1) { + // This can't possibly happen, but sanity check. + Slog.w(TAG, "Found multiple files with the same name: " + Arrays.toString(files)); + } + return parseFile(files[0]); + } + + /** + * Reads all files in directory and returns a map with file names as keys and parsed file + * contents as values. + */ + @WorkerThread + @Nullable + Map<String, T> readAll() { + File[] files = mRootDir.listFiles(File::isFile); + if (files == null) { + return null; + } + + Map<String, T> results = new ArrayMap<>(); + for (File file : files) { + T result = parseFile(file); + if (result != null) { + results.put(file.getName(), result); + } + } + return results; + } + + /** + * Schedules the specified data to be flushed to a file in the future. Subsequent + * calls for the same file before the flush occurs will replace the previous data but will not + * reset when the flush will occur. All unique files will be flushed at the same time. + */ + @MainThread + synchronized void scheduleSave(@NonNull String fileName, @NonNull T data) { + mScheduledFileDataMap.put(fileName, data); + + if (mScheduledExecutorService.isShutdown()) { + Slog.e(TAG, "Worker is shutdown, failed to schedule data saving."); + return; + } + + // Skip scheduling another flush when one is pending. + if (mScheduledFuture != null) { + return; + } + + mScheduledFuture = mScheduledExecutorService.schedule(this::flushScheduledData, + mWriteDelayMs, TimeUnit.MILLISECONDS); + } + + /** + * Saves specified data immediately on a background thread, and blocks until its completed. This + * is useful for when device is powering off. + */ + @MainThread + synchronized void saveImmediately(@NonNull String fileName, @NonNull T data) { + if (mScheduledExecutorService.isShutdown()) { + return; + } + // Cancel existing future. + if (mScheduledFuture != null) { + + // We shouldn't need to interrupt as this method and threaded task + // #flushScheduledData are both synchronized. + mScheduledFuture.cancel(true); + } + + mScheduledFileDataMap.put(fileName, data); + // Submit flush and blocks until it completes. Blocking will prevent the device from + // shutting down before flushing completes. + Future<?> future = mScheduledExecutorService.submit(this::flushScheduledData); + try { + future.get(SHUTDOWN_DISK_WRITE_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Slog.e(TAG, "Failed to save data immediately.", e); + } + } + + @WorkerThread + private synchronized void flushScheduledData() { + if (mScheduledFileDataMap.isEmpty()) { + mScheduledFuture = null; + return; + } + for (String fileName : mScheduledFileDataMap.keySet()) { + T data = mScheduledFileDataMap.remove(fileName); + writeTo(fileName, data); + } + mScheduledFuture = null; + } + + @WorkerThread + @Nullable + private T parseFile(@NonNull File file) { + final AtomicFile atomicFile = new AtomicFile(file); + try (FileInputStream fileInputStream = atomicFile.openRead()) { + final ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); + return protoStreamReader().read(protoInputStream); + } catch (IOException e) { + Slog.e(TAG, "Failed to parse protobuf file.", e); + } + return null; + } + + @NonNull + private File getFile(String fileName) { + return new File(mRootDir, fileName); + } + + /** + * {@code ProtoStreamWriter} writes {@code T} fields to {@link ProtoOutputStream}. + * + * @param <T> is the data class representation of a protobuf. + */ + interface ProtoStreamWriter<T> { + + /** + * Writes {@code T} to {@link ProtoOutputStream}. + */ + void write(@NonNull ProtoOutputStream protoOutputStream, @NonNull T data); + } + + /** + * {@code ProtoStreamReader} reads {@link ProtoInputStream} and translate it to {@code T}. + * + * @param <T> is the data class representation of a protobuf. + */ + interface ProtoStreamReader<T> { + /** + * Reads {@link ProtoInputStream} and translates it to {@code T}. + */ + @Nullable + T read(@NonNull ProtoInputStream protoInputStream); + } +} diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java index b60ed3e8783f..ce353667b62d 100644 --- a/services/people/java/com/android/server/people/data/ConversationInfo.java +++ b/services/people/java/com/android/server/people/data/ConversationInfo.java @@ -20,12 +20,18 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.LocusId; +import android.content.LocusIdProto; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo.ShortcutFlags; import android.net.Uri; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; +import com.android.server.people.ConversationInfoProto; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -35,6 +41,8 @@ import java.util.Objects; */ public class ConversationInfo { + private static final String TAG = ConversationInfo.class.getSimpleName(); + private static final int FLAG_IMPORTANT = 1; private static final int FLAG_NOTIFICATION_SILENCED = 1 << 1; @@ -241,6 +249,72 @@ public class ConversationInfo { return (mConversationFlags & flags) == flags; } + /** Writes field members to {@link ProtoOutputStream}. */ + void writeToProto(@NonNull ProtoOutputStream protoOutputStream) { + protoOutputStream.write(ConversationInfoProto.SHORTCUT_ID, mShortcutId); + if (mLocusId != null) { + long locusIdToken = protoOutputStream.start(ConversationInfoProto.LOCUS_ID_PROTO); + protoOutputStream.write(LocusIdProto.LOCUS_ID, mLocusId.getId()); + protoOutputStream.end(locusIdToken); + } + if (mContactUri != null) { + protoOutputStream.write(ConversationInfoProto.CONTACT_URI, mContactUri.toString()); + } + if (mNotificationChannelId != null) { + protoOutputStream.write(ConversationInfoProto.NOTIFICATION_CHANNEL_ID, + mNotificationChannelId); + } + protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags); + protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags); + } + + /** Reads from {@link ProtoInputStream} and constructs a {@link ConversationInfo}. */ + @NonNull + static ConversationInfo readFromProto(@NonNull ProtoInputStream protoInputStream) + throws IOException { + ConversationInfo.Builder builder = new ConversationInfo.Builder(); + while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (protoInputStream.getFieldNumber()) { + case (int) ConversationInfoProto.SHORTCUT_ID: + builder.setShortcutId( + protoInputStream.readString(ConversationInfoProto.SHORTCUT_ID)); + break; + case (int) ConversationInfoProto.LOCUS_ID_PROTO: + long locusIdToken = protoInputStream.start( + ConversationInfoProto.LOCUS_ID_PROTO); + while (protoInputStream.nextField() + != ProtoInputStream.NO_MORE_FIELDS) { + if (protoInputStream.getFieldNumber() == (int) LocusIdProto.LOCUS_ID) { + builder.setLocusId(new LocusId( + protoInputStream.readString(LocusIdProto.LOCUS_ID))); + } + } + protoInputStream.end(locusIdToken); + break; + case (int) ConversationInfoProto.CONTACT_URI: + builder.setContactUri(Uri.parse(protoInputStream.readString( + ConversationInfoProto.CONTACT_URI))); + break; + case (int) ConversationInfoProto.NOTIFICATION_CHANNEL_ID: + builder.setNotificationChannelId(protoInputStream.readString( + ConversationInfoProto.NOTIFICATION_CHANNEL_ID)); + break; + case (int) ConversationInfoProto.SHORTCUT_FLAGS: + builder.setShortcutFlags(protoInputStream.readInt( + ConversationInfoProto.SHORTCUT_FLAGS)); + break; + case (int) ConversationInfoProto.CONVERSATION_FLAGS: + builder.setConversationFlags(protoInputStream.readInt( + ConversationInfoProto.CONVERSATION_FLAGS)); + break; + default: + Slog.w(TAG, "Could not read undefined field: " + + protoInputStream.getFieldNumber()); + } + } + return builder.build(); + } + /** * Builder class for {@link ConversationInfo} objects. */ diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java index 364992181f75..ea36d38e5d4a 100644 --- a/services/people/java/com/android/server/people/data/ConversationStore.java +++ b/services/people/java/com/android/server/people/data/ConversationStore.java @@ -16,58 +16,124 @@ package com.android.server.people.data; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.WorkerThread; import android.content.LocusId; import android.net.Uri; +import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.ArrayMap; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import com.android.internal.annotations.GuardedBy; +import com.android.server.people.ConversationInfosProto; + +import com.google.android.collect.Lists; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; -/** The store that stores and accesses the conversations data for a package. */ +/** + * The store that stores and accesses the conversations data for a package. + */ class ConversationStore { + private static final String TAG = ConversationStore.class.getSimpleName(); + + private static final String CONVERSATIONS_FILE_NAME = "conversations"; + + private static final long DISK_WRITE_DELAY = 2L * DateUtils.MINUTE_IN_MILLIS; + // Shortcut ID -> Conversation Info + @GuardedBy("this") private final Map<String, ConversationInfo> mConversationInfoMap = new ArrayMap<>(); // Locus ID -> Shortcut ID + @GuardedBy("this") private final Map<LocusId, String> mLocusIdToShortcutIdMap = new ArrayMap<>(); // Contact URI -> Shortcut ID + @GuardedBy("this") private final Map<Uri, String> mContactUriToShortcutIdMap = new ArrayMap<>(); // Phone Number -> Shortcut ID + @GuardedBy("this") private final Map<String, String> mPhoneNumberToShortcutIdMap = new ArrayMap<>(); // Notification Channel ID -> Shortcut ID + @GuardedBy("this") private final Map<String, String> mNotifChannelIdToShortcutIdMap = new ArrayMap<>(); - void addOrUpdate(@NonNull ConversationInfo conversationInfo) { - mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo); + private final ScheduledExecutorService mScheduledExecutorService; + private final File mPackageDir; + private final ContactsQueryHelper mHelper; - LocusId locusId = conversationInfo.getLocusId(); - if (locusId != null) { - mLocusIdToShortcutIdMap.put(locusId, conversationInfo.getShortcutId()); - } + private ConversationInfosProtoDiskReadWriter mConversationInfosProtoDiskReadWriter; - Uri contactUri = conversationInfo.getContactUri(); - if (contactUri != null) { - mContactUriToShortcutIdMap.put(contactUri, conversationInfo.getShortcutId()); - } + ConversationStore(@NonNull File packageDir, + @NonNull ScheduledExecutorService scheduledExecutorService, + @NonNull ContactsQueryHelper helper) { + mScheduledExecutorService = scheduledExecutorService; + mPackageDir = packageDir; + mHelper = helper; + } - String phoneNumber = conversationInfo.getContactPhoneNumber(); - if (phoneNumber != null) { - mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId()); - } + /** + * Loads conversations from disk to memory in a background thread. This should be called + * after the device powers on and the user has been unlocked. + */ + @MainThread + void loadConversationsFromDisk() { + mScheduledExecutorService.submit(() -> { + synchronized (this) { + ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter = + getConversationInfosProtoDiskReadWriter(); + if (conversationInfosProtoDiskReadWriter == null) { + return; + } + List<ConversationInfo> conversationsOnDisk = + conversationInfosProtoDiskReadWriter.read(CONVERSATIONS_FILE_NAME); + if (conversationsOnDisk == null) { + return; + } + for (ConversationInfo conversationInfo : conversationsOnDisk) { + conversationInfo = restoreConversationPhoneNumber(conversationInfo); + updateConversationsInMemory(conversationInfo); + } + } + }); + } - String notifChannelId = conversationInfo.getNotificationChannelId(); - if (notifChannelId != null) { - mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId()); + /** + * Immediately flushes current conversations to disk. This should be called when device is + * powering off. + */ + @MainThread + synchronized void saveConversationsToDisk() { + ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter = + getConversationInfosProtoDiskReadWriter(); + if (conversationInfosProtoDiskReadWriter != null) { + conversationInfosProtoDiskReadWriter.saveConversationsImmediately( + new ArrayList<>(mConversationInfoMap.values())); } } - void deleteConversation(@NonNull String shortcutId) { + @MainThread + synchronized void addOrUpdate(@NonNull ConversationInfo conversationInfo) { + updateConversationsInMemory(conversationInfo); + scheduleUpdateConversationsOnDisk(); + } + + @MainThread + synchronized void deleteConversation(@NonNull String shortcutId) { ConversationInfo conversationInfo = mConversationInfoMap.remove(shortcutId); if (conversationInfo == null) { return; @@ -92,31 +158,32 @@ class ConversationStore { if (notifChannelId != null) { mNotifChannelIdToShortcutIdMap.remove(notifChannelId); } + scheduleUpdateConversationsOnDisk(); } - void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { + synchronized void forAllConversations(@NonNull Consumer<ConversationInfo> consumer) { for (ConversationInfo ci : mConversationInfoMap.values()) { consumer.accept(ci); } } @Nullable - ConversationInfo getConversation(@Nullable String shortcutId) { + synchronized ConversationInfo getConversation(@Nullable String shortcutId) { return shortcutId != null ? mConversationInfoMap.get(shortcutId) : null; } @Nullable - ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) { + synchronized ConversationInfo getConversationByLocusId(@NonNull LocusId locusId) { return getConversation(mLocusIdToShortcutIdMap.get(locusId)); } @Nullable - ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) { + synchronized ConversationInfo getConversationByContactUri(@NonNull Uri contactUri) { return getConversation(mContactUriToShortcutIdMap.get(contactUri)); } @Nullable - ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { + synchronized ConversationInfo getConversationByPhoneNumber(@NonNull String phoneNumber) { return getConversation(mPhoneNumberToShortcutIdMap.get(phoneNumber)); } @@ -124,4 +191,140 @@ class ConversationStore { ConversationInfo getConversationByNotificationChannelId(@NonNull String notifChannelId) { return getConversation(mNotifChannelIdToShortcutIdMap.get(notifChannelId)); } + + @MainThread + private synchronized void updateConversationsInMemory( + @NonNull ConversationInfo conversationInfo) { + mConversationInfoMap.put(conversationInfo.getShortcutId(), conversationInfo); + + LocusId locusId = conversationInfo.getLocusId(); + if (locusId != null) { + mLocusIdToShortcutIdMap.put(locusId, conversationInfo.getShortcutId()); + } + + Uri contactUri = conversationInfo.getContactUri(); + if (contactUri != null) { + mContactUriToShortcutIdMap.put(contactUri, conversationInfo.getShortcutId()); + } + + String phoneNumber = conversationInfo.getContactPhoneNumber(); + if (phoneNumber != null) { + mPhoneNumberToShortcutIdMap.put(phoneNumber, conversationInfo.getShortcutId()); + } + + String notifChannelId = conversationInfo.getNotificationChannelId(); + if (notifChannelId != null) { + mNotifChannelIdToShortcutIdMap.put(notifChannelId, conversationInfo.getShortcutId()); + } + } + + /** Schedules a dump of all conversations onto disk, overwriting existing values. */ + @MainThread + private synchronized void scheduleUpdateConversationsOnDisk() { + ConversationInfosProtoDiskReadWriter conversationInfosProtoDiskReadWriter = + getConversationInfosProtoDiskReadWriter(); + if (conversationInfosProtoDiskReadWriter != null) { + conversationInfosProtoDiskReadWriter.scheduleConversationsSave( + new ArrayList<>(mConversationInfoMap.values())); + } + } + + @Nullable + private ConversationInfosProtoDiskReadWriter getConversationInfosProtoDiskReadWriter() { + if (!mPackageDir.exists()) { + Slog.e(TAG, "Package data directory does not exist: " + mPackageDir.getAbsolutePath()); + return null; + } + if (mConversationInfosProtoDiskReadWriter == null) { + mConversationInfosProtoDiskReadWriter = new ConversationInfosProtoDiskReadWriter( + mPackageDir, CONVERSATIONS_FILE_NAME, DISK_WRITE_DELAY, + mScheduledExecutorService); + } + return mConversationInfosProtoDiskReadWriter; + } + + /** + * Conversation's phone number is not saved on disk, so it has to be fetched. + */ + @WorkerThread + private ConversationInfo restoreConversationPhoneNumber( + @NonNull ConversationInfo conversationInfo) { + if (conversationInfo.getContactUri() != null) { + if (mHelper.query(conversationInfo.getContactUri().toString())) { + String phoneNumber = mHelper.getPhoneNumber(); + if (!TextUtils.isEmpty(phoneNumber)) { + conversationInfo = new ConversationInfo.Builder( + conversationInfo).setContactPhoneNumber( + phoneNumber).build(); + } + } + } + return conversationInfo; + } + + /** Reads and writes {@link ConversationInfo} on disk. */ + static class ConversationInfosProtoDiskReadWriter extends + AbstractProtoDiskReadWriter<List<ConversationInfo>> { + + private final String mConversationInfoFileName; + + ConversationInfosProtoDiskReadWriter(@NonNull File baseDir, + @NonNull String conversationInfoFileName, + long writeDelayMs, @NonNull ScheduledExecutorService scheduledExecutorService) { + super(baseDir, writeDelayMs, scheduledExecutorService); + mConversationInfoFileName = conversationInfoFileName; + } + + @Override + ProtoStreamWriter<List<ConversationInfo>> protoStreamWriter() { + return (protoOutputStream, data) -> { + for (ConversationInfo conversationInfo : data) { + long token = protoOutputStream.start(ConversationInfosProto.CONVERSATION_INFOS); + conversationInfo.writeToProto(protoOutputStream); + protoOutputStream.end(token); + } + }; + } + + @Override + ProtoStreamReader<List<ConversationInfo>> protoStreamReader() { + return protoInputStream -> { + List<ConversationInfo> results = Lists.newArrayList(); + try { + while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (protoInputStream.getFieldNumber() + != (int) ConversationInfosProto.CONVERSATION_INFOS) { + continue; + } + long token = protoInputStream.start( + ConversationInfosProto.CONVERSATION_INFOS); + ConversationInfo conversationInfo = ConversationInfo.readFromProto( + protoInputStream); + protoInputStream.end(token); + results.add(conversationInfo); + } + } catch (IOException e) { + Slog.e(TAG, "Failed to read protobuf input stream.", e); + } + return results; + }; + } + + /** + * Schedules a flush of the specified conversations to disk. + */ + @MainThread + void scheduleConversationsSave(@NonNull List<ConversationInfo> conversationInfos) { + scheduleSave(mConversationInfoFileName, conversationInfos); + } + + /** + * Saves the specified conversations immediately. This should be used when device is + * powering off. + */ + @MainThread + void saveConversationsImmediately(@NonNull List<ConversationInfo> conversationInfos) { + saveImmediately(mConversationInfoFileName, conversationInfos); + } + } } diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index 7a3ed5348d30..c8673f833e71 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -86,6 +86,7 @@ public class DataManager { private final Context mContext; private final Injector mInjector; private final ScheduledExecutorService mUsageStatsQueryExecutor; + private final ScheduledExecutorService mDiskReadWriterExecutor; private final SparseArray<UserData> mUserDataArray = new SparseArray<>(); private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>(); @@ -113,6 +114,7 @@ public class DataManager { BackgroundThread.getHandler()); mMmsSmsContentObserver = new MmsSmsContentObserver( BackgroundThread.getHandler()); + mDiskReadWriterExecutor = mInjector.createScheduledExecutor(); } /** Initialization. Called when the system services are up running. */ @@ -122,13 +124,18 @@ public class DataManager { mUserManager = mContext.getSystemService(UserManager.class); mShortcutServiceInternal.addListener(new ShortcutServiceListener()); + + IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); + BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver(); + mContext.registerReceiver(shutdownBroadcastReceiver, shutdownIntentFilter); } /** This method is called when a user is unlocked. */ public void onUserUnlocked(int userId) { UserData userData = mUserDataArray.get(userId); if (userData == null) { - userData = new UserData(userId); + userData = new UserData(userId, mDiskReadWriterExecutor, + mInjector.createContactsQueryHelper(mContext)); mUserDataArray.put(userId, userData); } userData.setUserUnlocked(); @@ -662,6 +669,14 @@ public class DataManager { } } + private class ShutdownBroadcastReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + forAllPackages(PackageData::saveToDisk); + } + } + @VisibleForTesting static class Injector { diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java index 75b870c74591..f67699c28531 100644 --- a/services/people/java/com/android/server/people/data/PackageData.java +++ b/services/people/java/com/android/server/people/data/PackageData.java @@ -22,6 +22,8 @@ import android.annotation.UserIdInt; import android.content.LocusId; import android.text.TextUtils; +import java.io.File; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; import java.util.function.Predicate; @@ -43,17 +45,36 @@ public class PackageData { private final Predicate<String> mIsDefaultSmsAppPredicate; + private final File mPackageDataDir; + PackageData(@NonNull String packageName, @UserIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, - @NonNull Predicate<String> isDefaultSmsAppPredicate) { + @NonNull Predicate<String> isDefaultSmsAppPredicate, + @NonNull ScheduledExecutorService scheduledExecutorService, + @NonNull File perUserPeopleDataDir, + @NonNull ContactsQueryHelper helper) { mPackageName = packageName; mUserId = userId; - mConversationStore = new ConversationStore(); + + mPackageDataDir = new File(perUserPeopleDataDir, mPackageName); + mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService, + helper); mEventStore = new EventStore(); mIsDefaultDialerPredicate = isDefaultDialerPredicate; mIsDefaultSmsAppPredicate = isDefaultSmsAppPredicate; } + /** Called when user is unlocked. */ + void loadFromDisk() { + mPackageDataDir.mkdirs(); + mConversationStore.loadConversationsFromDisk(); + } + + /** Called when device is shutting down. */ + void saveToDisk() { + mConversationStore.saveConversationsToDisk(); + } + @NonNull public String getPackageName() { return mPackageName; diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java index 4e8fd16d05fd..aaa5db878e08 100644 --- a/services/people/java/com/android/server/people/data/UserData.java +++ b/services/people/java/com/android/server/people/data/UserData.java @@ -19,10 +19,13 @@ package com.android.server.people.data; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.os.Environment; import android.text.TextUtils; import android.util.ArrayMap; +import java.io.File; import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Consumer; /** The data associated with a user profile. */ @@ -30,6 +33,12 @@ class UserData { private final @UserIdInt int mUserId; + private final File mPerUserPeopleDataDir; + + private final ScheduledExecutorService mScheduledExecutorService; + + private final ContactsQueryHelper mHelper; + private boolean mIsUnlocked; private Map<String, PackageData> mPackageDataMap = new ArrayMap<>(); @@ -40,8 +49,12 @@ class UserData { @Nullable private String mDefaultSmsApp; - UserData(@UserIdInt int userId) { + UserData(@UserIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService, + ContactsQueryHelper helper) { mUserId = userId; + mPerUserPeopleDataDir = new File(Environment.getDataSystemCeDirectory(mUserId), "people"); + mScheduledExecutorService = scheduledExecutorService; + mHelper = helper; } @UserIdInt int getUserId() { @@ -56,6 +69,13 @@ class UserData { void setUserUnlocked() { mIsUnlocked = true; + + // Ensures per user root directory for people data is present, and attempt to load + // data from disk. + mPerUserPeopleDataDir.mkdirs(); + for (PackageData packageData : mPackageDataMap.values()) { + packageData.loadFromDisk(); + } } void setUserStopped() { @@ -103,7 +123,8 @@ class UserData { } private PackageData createPackageData(String packageName) { - return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp); + return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp, + mScheduledExecutorService, mPerUserPeopleDataDir, mHelper); } private boolean isDefaultDialer(String packageName) { diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java index 223a98b6c8f6..8daef5fad032 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java @@ -67,8 +67,7 @@ public class ShadowPerformUnifiedRestoreTask { int pmToken, boolean isFullSystemRestore, @Nullable String[] filterSet, - OnTaskFinishedListener listener, - Map<String, Set<String>> excludedKeys) { + OnTaskFinishedListener listener) { mBackupManagerService = backupManagerService; mPackage = targetPackage; mIsFullSystemRestore = isFullSystemRestore; diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java index fbb55fdeeb8f..5a96347c4ae1 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java @@ -25,7 +25,9 @@ import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static org.mockito.AdditionalAnswers.returnsFirstArg; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -133,6 +135,8 @@ public class AccessibilitySecurityPolicyTest { mA11ySecurityPolicy = new AccessibilitySecurityPolicy(mMockContext, mMockA11yUserManager); mA11ySecurityPolicy.setAccessibilityWindowManager(mMockA11yWindowManager); mA11ySecurityPolicy.setAppWidgetManager(mMockAppWidgetManager); + + when(mMockA11yWindowManager.resolveParentWindowIdLocked(anyInt())).then(returnsFirstArg()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java index 9db5a080c093..10a86f9ea527 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java @@ -88,6 +88,10 @@ public class AccessibilityWindowManagerTest { private static final int DEFAULT_FOCUSED_INDEX = 1; private static final int SCREEN_WIDTH = 1080; private static final int SCREEN_HEIGHT = 1920; + private static final int INVALID_ID = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; + private static final int HOST_WINDOW_ID = 10; + private static final int EMBEDDED_WINDOW_ID = 11; + private static final int OTHER_WINDOW_ID = 12; private AccessibilityWindowManager mA11yWindowManager; // Window manager will support multiple focused window if config_perDisplayFocusEnabled is true, @@ -117,6 +121,10 @@ public class AccessibilityWindowManagerTest { @Mock private AccessibilitySecurityPolicy mMockA11ySecurityPolicy; @Mock private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager; + @Mock private IBinder mMockHostToken; + @Mock private IBinder mMockEmbeddedToken; + @Mock private IBinder mMockInvalidToken; + @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); @@ -140,6 +148,8 @@ public class AccessibilityWindowManagerTest { // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged. // Resets it for mockito verify of further test case. Mockito.reset(mMockA11yEventSender); + + registerLeashedTokenAndWindowId(); } @After @@ -407,6 +417,51 @@ public class AccessibilityWindowManagerTest { } @Test + public void resolveParentWindowId_windowIsNotEmbedded_shouldReturnGivenId() + throws RemoteException { + final int windowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, false, + Mockito.mock(IBinder.class), USER_SYSTEM_ID); + assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId)); + } + + @Test + public void resolveParentWindowId_windowIsNotRegistered_shouldReturnGivenId() { + final int windowId = -1; + assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId)); + } + + @Test + public void resolveParentWindowId_windowIsAssociated_shouldReturnParentWindowId() + throws RemoteException { + final IBinder mockHostToken = Mockito.mock(IBinder.class); + final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class); + final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockHostToken, USER_SYSTEM_ID); + final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockEmbeddedToken, USER_SYSTEM_ID); + mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken); + final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked( + embeddedWindowId); + assertEquals(hostWindowId, resolvedWindowId); + } + + @Test + public void resolveParentWindowId_windowIsDisassociated_shouldReturnGivenId() + throws RemoteException { + final IBinder mockHostToken = Mockito.mock(IBinder.class); + final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class); + final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockHostToken, USER_SYSTEM_ID); + final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, + false, mockEmbeddedToken, USER_SYSTEM_ID); + mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken); + mA11yWindowManager.disassociateEmbeddedHierarchyLocked(mockEmbeddedToken); + final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked( + embeddedWindowId); + assertEquals(embeddedWindowId, resolvedWindowId); + } + + @Test public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() { // Updates top 2 z-order WindowInfo are whole visible. WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0); @@ -726,6 +781,64 @@ public class AccessibilityWindowManagerTest { token.asBinder(), -1); } + @Test + public void getHostTokenLocked_hierarchiesAreAssociated_shouldReturnHostToken() { + mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken); + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken); + assertEquals(hostToken, mMockHostToken); + } + + @Test + public void getHostTokenLocked_hierarchiesAreNotAssociated_shouldReturnNull() { + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken); + assertNull(hostToken); + } + + @Test + public void getHostTokenLocked_embeddedHierarchiesAreDisassociated_shouldReturnNull() { + mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken); + mA11yWindowManager.disassociateLocked(mMockEmbeddedToken); + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken); + assertNull(hostToken); + } + + @Test + public void getHostTokenLocked_hostHierarchiesAreDisassociated_shouldReturnNull() { + mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken); + mA11yWindowManager.disassociateLocked(mMockHostToken); + final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockHostToken); + assertNull(hostToken); + } + + @Test + public void getWindowIdLocked_windowIsRegistered_shouldReturnWindowId() { + final int windowId = mA11yWindowManager.getWindowIdLocked(mMockHostToken); + assertEquals(windowId, HOST_WINDOW_ID); + } + + @Test + public void getWindowIdLocked_windowIsNotRegistered_shouldReturnInvalidWindowId() { + final int windowId = mA11yWindowManager.getWindowIdLocked(mMockInvalidToken); + assertEquals(windowId, INVALID_ID); + } + + @Test + public void getTokenLocked_windowIsRegistered_shouldReturnToken() { + final IBinder token = mA11yWindowManager.getTokenLocked(HOST_WINDOW_ID); + assertEquals(token, mMockHostToken); + } + + @Test + public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() { + final IBinder token = mA11yWindowManager.getTokenLocked(OTHER_WINDOW_ID); + assertNull(token); + } + + private void registerLeashedTokenAndWindowId() { + mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID); + mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID); + } + private void startTrackingPerDisplay(int displayId) throws RemoteException { ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>(); // Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy @@ -784,6 +897,7 @@ public class AccessibilityWindowManagerTest { IAccessibilityInteractionConnection.class); final IBinder mockConnectionBinder = Mockito.mock(IBinder.class); final IBinder mockWindowBinder = Mockito.mock(IBinder.class); + final IBinder mockLeashToken = Mockito.mock(IBinder.class); when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder); when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder); when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId)) @@ -792,11 +906,31 @@ public class AccessibilityWindowManagerTest { .thenReturn(displayId); int windowId = mA11yWindowManager.addAccessibilityInteractionConnection( - mockWindowToken, mockA11yConnection, PACKAGE_NAME, userId); + mockWindowToken, mockLeashToken, mockA11yConnection, PACKAGE_NAME, userId); mA11yWindowTokens.put(windowId, mockWindowToken); return mockWindowToken; } + private int addAccessibilityInteractionConnection(int displayId, boolean bGlobal, + IBinder leashToken, int userId) throws RemoteException { + final IWindow mockWindowToken = Mockito.mock(IWindow.class); + final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock( + IAccessibilityInteractionConnection.class); + final IBinder mockConnectionBinder = Mockito.mock(IBinder.class); + final IBinder mockWindowBinder = Mockito.mock(IBinder.class); + when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder); + when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder); + when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId)) + .thenReturn(bGlobal); + when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowToken.asBinder())) + .thenReturn(displayId); + + int windowId = mA11yWindowManager.addAccessibilityInteractionConnection( + mockWindowToken, leashToken, mockA11yConnection, PACKAGE_NAME, userId); + mA11yWindowTokens.put(windowId, mockWindowToken); + return windowId; + } + private void addWindowInfo(ArrayList<WindowInfo> windowInfos, IWindow windowToken, int layer) { final WindowInfo windowInfo = WindowInfo.obtain(); windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION; diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java index d6efe35723db..5800acabe8a5 100644 --- a/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupPreferencesTest.java @@ -16,8 +16,6 @@ package com.android.server.backup; -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import androidx.test.InstrumentationRegistry; @@ -33,18 +31,15 @@ import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; @Presubmit @RunWith(AndroidJUnit4.class) public class UserBackupPreferencesTest { private static final String EXCLUDED_PACKAGE_1 = "package1"; - private static final String EXCLUDED_PACKAGE_2 = "package2"; private static final List<String> EXCLUDED_KEYS_1 = Arrays.asList("key1", "key2"); - private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key1"); + private static final List<String> EXCLUDED_KEYS_2 = Arrays.asList("key3"); @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); @@ -60,27 +55,13 @@ public class UserBackupPreferencesTest { } @Test - public void testGetExcludedKeysForPackages_returnsExcludedKeys() { + public void testGetExcludedKeysForPackage_returnsExcludedKeys() { mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1); - mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2); + mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_2); - Map<String, Set<String>> excludedKeys = - mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackages(EXCLUDED_PACKAGE_1); - assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1)); - assertFalse(excludedKeys.containsKey(EXCLUDED_PACKAGE_2)); - assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1)); - } - - @Test - public void testGetExcludedKeysForPackages_withEmpty_list_returnsAllExcludedKeys() { - mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_1, EXCLUDED_KEYS_1); - mExcludedRestoreKeysStorage.addExcludedKeys(EXCLUDED_PACKAGE_2, EXCLUDED_KEYS_2); - - Map<String, Set<String>> excludedKeys = - mExcludedRestoreKeysStorage.getAllExcludedRestoreKeys(); - assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_1)); - assertTrue(excludedKeys.containsKey(EXCLUDED_PACKAGE_2)); - assertEquals(new HashSet<>(EXCLUDED_KEYS_1), excludedKeys.get(EXCLUDED_PACKAGE_1)); - assertEquals(new HashSet<>(EXCLUDED_KEYS_2), excludedKeys.get(EXCLUDED_PACKAGE_2)); + Set<String> excludedKeys = + mExcludedRestoreKeysStorage.getExcludedRestoreKeysForPackage(EXCLUDED_PACKAGE_1); + assertTrue(excludedKeys.containsAll(EXCLUDED_KEYS_1)); + assertTrue(excludedKeys.containsAll(EXCLUDED_KEYS_2)); } } diff --git a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java index 3d220432cc8e..017c93975286 100644 --- a/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java @@ -20,7 +20,9 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import androidx.test.InstrumentationRegistry; @@ -30,6 +32,8 @@ import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.platform.test.annotations.Presubmit; +import com.android.server.backup.UserBackupManagerService; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +44,7 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.ArrayDeque; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -59,6 +64,7 @@ public class PerformUnifiedRestoreTaskTest { @Mock private BackupDataInput mBackupDataInput; @Mock private BackupDataOutput mBackupDataOutput; + @Mock private UserBackupManagerService mBackupManagerService; private Set<String> mExcludedkeys = new HashSet<>(); private Map<String, String> mBackupData = new HashMap<>(); @@ -99,6 +105,8 @@ public class PerformUnifiedRestoreTaskTest { return null; } }); + + mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService); } private void populateTestData() { @@ -114,8 +122,9 @@ public class PerformUnifiedRestoreTaskTest { @Test public void testFilterExcludedKeys() throws Exception { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( - PACKAGE_NAME, mExcludedkeys)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn( + mExcludedkeys); + mRestoreTask.filterExcludedKeys(PACKAGE_NAME, mBackupDataInput, mBackupDataOutput); // Verify only the correct were written into BackupDataOutput object. @@ -125,32 +134,49 @@ public class PerformUnifiedRestoreTaskTest { } @Test + public void testGetExcludedKeysForPackage_alwaysReturnsLatestKeys() { + Set<String> firstExcludedKeys = new HashSet<>(Collections.singletonList(EXCLUDED_KEY_1)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn( + firstExcludedKeys); + assertEquals(firstExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME)); + + + Set<String> secondExcludedKeys = new HashSet<>(Arrays.asList(EXCLUDED_KEY_1, + EXCLUDED_KEY_2)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))).thenReturn( + secondExcludedKeys); + assertEquals(secondExcludedKeys, mRestoreTask.getExcludedKeysForPackage(PACKAGE_NAME)); + } + + @Test public void testStageBackupData_stageForNonSystemPackageWithKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( - PACKAGE_NAME, mExcludedkeys)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(NON_SYSTEM_PACKAGE_NAME))).thenReturn( + mExcludedkeys); assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); } @Test public void testStageBackupData_stageForNonSystemPackageWithNoKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap()); + when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn( + Collections.emptySet()); assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); } @Test public void testStageBackupData_doNotStageForSystemPackageWithNoKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.emptyMap()); + when(mBackupManagerService.getExcludedRestoreKeys(any())).thenReturn( + Collections.emptySet()); assertFalse(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME)); } @Test public void testStageBackupData_stageForSystemPackageWithKeysToExclude() { - mRestoreTask = new PerformUnifiedRestoreTask(Collections.singletonMap( - PACKAGE_NAME, mExcludedkeys)); + when(mBackupManagerService.getExcludedRestoreKeys(eq(SYSTEM_PACKAGE_NAME))).thenReturn( + mExcludedkeys); - assertTrue(mRestoreTask.shouldStageBackupData(NON_SYSTEM_PACKAGE_NAME)); + assertTrue(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME)); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 156cd6e5826d..e5adb80e6ef9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -283,7 +283,7 @@ public class BiometricServiceTest { null /* authenticators */); waitForIdle(); verify(mReceiver1).onError( - eq(BiometricAuthenticator.TYPE_NONE), + eq(BiometricAuthenticator.TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(0 /* vendorCode */)); } @@ -1117,14 +1117,14 @@ public class BiometricServiceTest { // STRONG-only auth is not available int authenticators = Authenticators.BIOMETRIC_STRONG; - assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE, + assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED, invokeCanAuthenticate(mBiometricService, authenticators)); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, authenticators); waitForIdle(); verify(mReceiver1).onError( - eq(BiometricAuthenticator.TYPE_NONE), - eq(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT), + eq(BiometricAuthenticator.TYPE_FINGERPRINT), + eq(BiometricPrompt.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED), eq(0) /* vendorCode */); // Request for weak auth works @@ -1154,7 +1154,7 @@ public class BiometricServiceTest { false /* requireConfirmation */, authenticators); waitForIdle(); - assertTrue(Utils.isDeviceCredentialAllowed(mBiometricService.mCurrentAuthSession.mBundle)); + assertTrue(Utils.isCredentialRequested(mBiometricService.mCurrentAuthSession.mBundle)); verify(mBiometricService.mStatusBarService).showAuthenticationDialog( eq(mBiometricService.mCurrentAuthSession.mBundle), any(IBiometricServiceReceiverInternal.class), @@ -1162,6 +1162,28 @@ public class BiometricServiceTest { anyBoolean() /* requireConfirmation */, anyInt() /* userId */, eq(TEST_PACKAGE_NAME)); + + // Un-downgrading the authenticator allows successful strong auth + for (BiometricService.AuthenticatorWrapper wrapper : mBiometricService.mAuthenticators) { + if (wrapper.id == testId) { + wrapper.updateStrength(Authenticators.BIOMETRIC_STRONG); + } + } + + resetReceiver(); + authenticators = Authenticators.BIOMETRIC_STRONG; + assertEquals(BiometricManager.BIOMETRIC_SUCCESS, + invokeCanAuthenticate(mBiometricService, authenticators)); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */, authenticators); + waitForIdle(); + verify(mBiometricService.mStatusBarService).showAuthenticationDialog( + eq(mBiometricService.mCurrentAuthSession.mBundle), + any(IBiometricServiceReceiverInternal.class), + eq(BiometricAuthenticator.TYPE_FINGERPRINT /* biometricModality */), + anyBoolean() /* requireConfirmation */, + anyInt() /* userId */, + eq(TEST_PACKAGE_NAME)); } @Test(expected = IllegalStateException.class) @@ -1245,6 +1267,19 @@ public class BiometricServiceTest { } @Test + public void testAuthentication_normalAppIgnoresDevicePolicy() throws Exception { + setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + when(mDevicePolicyManager + .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG); + waitForIdle(); + assertEquals(mBiometricService.mCurrentAuthSession.mState, + BiometricService.STATE_AUTH_STARTED); + } + + @Test public void testWorkAuthentication_faceWorksIfNotDisabledByDevicePolicyManager() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java index 312ff2ca84a1..df242f3efc16 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java @@ -91,31 +91,31 @@ public class UtilsTest { @Test public void testIsDeviceCredentialAllowed_withIntegerFlags() { int authenticators = 0; - assertFalse(Utils.isDeviceCredentialAllowed(authenticators)); + assertFalse(Utils.isCredentialRequested(authenticators)); authenticators |= Authenticators.DEVICE_CREDENTIAL; - assertTrue(Utils.isDeviceCredentialAllowed(authenticators)); + assertTrue(Utils.isCredentialRequested(authenticators)); authenticators |= Authenticators.BIOMETRIC_WEAK; - assertTrue(Utils.isDeviceCredentialAllowed(authenticators)); + assertTrue(Utils.isCredentialRequested(authenticators)); } @Test public void testIsDeviceCredentialAllowed_withBundle() { Bundle bundle = new Bundle(); - assertFalse(Utils.isDeviceCredentialAllowed(bundle)); + assertFalse(Utils.isCredentialRequested(bundle)); int authenticators = 0; bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); - assertFalse(Utils.isDeviceCredentialAllowed(bundle)); + assertFalse(Utils.isCredentialRequested(bundle)); authenticators |= Authenticators.DEVICE_CREDENTIAL; bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); - assertTrue(Utils.isDeviceCredentialAllowed(bundle)); + assertTrue(Utils.isCredentialRequested(bundle)); authenticators |= Authenticators.BIOMETRIC_WEAK; bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); - assertTrue(Utils.isDeviceCredentialAllowed(bundle)); + assertTrue(Utils.isCredentialRequested(bundle)); } @Test @@ -140,14 +140,14 @@ public class UtilsTest { for (int i = 0; i <= 7; i++) { int authenticators = 1 << i; bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); - assertTrue(Utils.isBiometricAllowed(bundle)); + assertTrue(Utils.isBiometricRequested(bundle)); } // The rest of the bits are not allowed to integrate with the public APIs for (int i = 8; i < 32; i++) { int authenticators = 1 << i; bundle.putInt(BiometricPrompt.KEY_AUTHENTICATORS_ALLOWED, authenticators); - assertFalse(Utils.isBiometricAllowed(bundle)); + assertFalse(Utils.isBiometricRequested(bundle)); } } diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index d40130a62fd9..8dae48cafd7b 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -211,7 +211,8 @@ public class AppIntegrityManagerServiceImplTest { IntentSender mockReceiver = mock(IntentSender.class); List<Rule> rules = Arrays.asList( - new Rule(IntegrityFormula.PACKAGE_NAME.equalTo(PACKAGE_NAME), Rule.DENY)); + new Rule(IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), + Rule.DENY)); mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver); runJobInHandler(); @@ -230,7 +231,8 @@ public class AppIntegrityManagerServiceImplTest { IntentSender mockReceiver = mock(IntentSender.class); List<Rule> rules = Arrays.asList( - new Rule(IntegrityFormula.PACKAGE_NAME.equalTo(PACKAGE_NAME), Rule.DENY)); + new Rule(IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), + Rule.DENY)); mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver); runJobInHandler(); @@ -390,7 +392,7 @@ public class AppIntegrityManagerServiceImplTest { public void getCurrentRules() throws Exception { whitelistUsAsRuleProvider(); makeUsSystemApp(); - Rule rule = new Rule(IntegrityFormula.PACKAGE_NAME.equalTo("package"), Rule.DENY); + Rule rule = new Rule(IntegrityFormula.Application.packageNameEquals("package"), Rule.DENY); when(mIntegrityFileManager.readRules(any())).thenReturn(Arrays.asList(rule)); assertThat(mService.getCurrentRules().getList()).containsExactly(rule); diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java index 38cf562f8c5b..3dc26afdb9af 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java @@ -58,7 +58,7 @@ public class RuleBinaryParserTest { getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS); private static final String ATOMIC_FORMULA_START_BITS = getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS); - private static final int INVALID_FORMULA_SEPARATOR_VALUE = 3; + private static final int INVALID_FORMULA_SEPARATOR_VALUE = (1 << SEPARATOR_BITS) - 1; private static final String INVALID_FORMULA_SEPARATOR_BITS = getBits(INVALID_FORMULA_SEPARATOR_VALUE, SEPARATOR_BITS); diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java index 913aff7daaa9..ea9e6ff86728 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java @@ -112,6 +112,7 @@ public class RuleIndexingDetailsIdentifierTest { ATOMIC_FORMULA_WITH_VERSION_CODE, ATOMIC_FORMULA_WITH_ISPREINSTALLED)), Rule.DENY); + public static final int INVALID_FORMULA_TAG = -1; @Test public void getIndexType_nullRule() { @@ -290,7 +291,7 @@ public class RuleIndexingDetailsIdentifierTest { return new AtomicFormula(0) { @Override public int getTag() { - return 4; + return INVALID_FORMULA_TAG; } @Override diff --git a/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java index f16cf35bfb34..4ba584e67929 100644 --- a/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/location/gnss/GnssManagerServiceTest.java @@ -39,6 +39,7 @@ import android.location.GnssClock; import android.location.GnssMeasurementCorrections; import android.location.GnssMeasurementsEvent; import android.location.GnssNavigationMessage; +import android.location.GnssRequest; import android.location.GnssSingleSatCorrection; import android.location.IBatchedLocationCallback; import android.location.IGnssMeasurementsListener; @@ -563,7 +564,7 @@ public class GnssManagerServiceTest { assertThrows(SecurityException.class, () -> mGnssManagerService.addGnssMeasurementsListener( - mockGnssMeasurementsListener, + new GnssRequest.Builder().build(), mockGnssMeasurementsListener, "com.android.server", "abcd123", "TestGnssMeasurementsListener")); mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent); @@ -580,8 +581,11 @@ public class GnssManagerServiceTest { enableLocationPermissions(); - assertThat(mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener, - "com.android.server", "abcd123", "TestGnssMeasurementsListener")).isEqualTo(true); + assertThat(mGnssManagerService.addGnssMeasurementsListener( + new GnssRequest.Builder().build(), + mockGnssMeasurementsListener, + "com.android.server", "abcd123", + "TestGnssMeasurementsListener")).isEqualTo(true); mTestGnssMeasurementsProvider.onMeasurementsAvailable(gnssMeasurementsEvent); verify(mockGnssMeasurementsListener, times(1)).onGnssMeasurementsReceived( @@ -626,8 +630,10 @@ public class GnssManagerServiceTest { enableLocationPermissions(); - mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener, - "com.android.server", "abcd123", "TestGnssMeasurementsListener"); + mGnssManagerService.addGnssMeasurementsListener(new GnssRequest.Builder().build(), + mockGnssMeasurementsListener, + "com.android.server", "abcd123", + "TestGnssMeasurementsListener"); disableLocationPermissions(); @@ -648,8 +654,10 @@ public class GnssManagerServiceTest { enableLocationPermissions(); - mGnssManagerService.addGnssMeasurementsListener(mockGnssMeasurementsListener, - "com.android.server", "abcd123", "TestGnssMeasurementsListener"); + mGnssManagerService.addGnssMeasurementsListener(new GnssRequest.Builder().build(), + mockGnssMeasurementsListener, + "com.android.server", "abcd123", + "TestGnssMeasurementsListener"); disableLocationPermissions(); diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java index 4ae374abb7c2..9213e1fe5a25 100644 --- a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java @@ -51,6 +51,7 @@ public final class PeopleServiceTest { private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share"; private static final int APP_PREDICTION_TARGET_COUNT = 4; private static final String TEST_PACKAGE_NAME = "com.example"; + private static final int USER_ID = 0; private PeopleServiceInternal mServiceInternal; private PeopleService.LocalService mLocalService; @@ -73,7 +74,7 @@ public final class PeopleServiceTest { mServiceInternal = LocalServices.getService(PeopleServiceInternal.class); mLocalService = (PeopleService.LocalService) mServiceInternal; - mSessionId = new AppPredictionSessionId("abc"); + mSessionId = new AppPredictionSessionId("abc", USER_ID); mPredictionContext = new AppPredictionContext.Builder(mContext) .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE) .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT) diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java index 331ad5972fc1..03b5e38cadb7 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java @@ -21,16 +21,24 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import android.annotation.Nullable; +import android.content.Context; import android.content.LocusId; import android.content.pm.ShortcutInfo; import android.net.Uri; +import android.os.FileUtils; +import android.text.format.DateUtils; import android.util.ArraySet; +import androidx.test.InstrumentationRegistry; + +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.File; import java.util.Set; @RunWith(JUnit4.class) @@ -42,11 +50,34 @@ public final class ConversationStoreTest { private static final Uri CONTACT_URI = Uri.parse("tel:+1234567890"); private static final String PHONE_NUMBER = "+1234567890"; + private static final String SHORTCUT_ID_2 = "ghi"; + private static final String NOTIFICATION_CHANNEL_ID_2 = "test : ghi"; + private static final LocusId LOCUS_ID_2 = new LocusId("jkl"); + private static final Uri CONTACT_URI_2 = Uri.parse("tel:+3234567890"); + private static final String PHONE_NUMBER_2 = "+3234567890"; + + private static final String SHORTCUT_ID_3 = "mno"; + private static final String NOTIFICATION_CHANNEL_ID_3 = "test : mno"; + private static final LocusId LOCUS_ID_3 = new LocusId("pqr"); + private static final Uri CONTACT_URI_3 = Uri.parse("tel:+9234567890"); + private static final String PHONE_NUMBER_3 = "+9234567890"; + + private MockScheduledExecutorService mMockScheduledExecutorService; + private TestContactQueryHelper mTestContactQueryHelper; private ConversationStore mConversationStore; + private File mFile; @Before public void setUp() { - mConversationStore = new ConversationStore(); + Context ctx = InstrumentationRegistry.getContext(); + mFile = new File(ctx.getCacheDir(), "testdir"); + mTestContactQueryHelper = new TestContactQueryHelper(ctx); + resetConversationStore(); + } + + @After + public void tearDown() { + FileUtils.deleteContentsAndDir(mFile); } @Test @@ -153,6 +184,130 @@ public final class ConversationStoreTest { mConversationStore.getConversationByNotificationChannelId(NOTIFICATION_CHANNEL_ID)); } + @Test + public void testDataPersistenceAndRestoration() { + // Add conversation infos, causing it to be loaded to disk. + ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2, + PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2); + ConversationInfo in3 = buildConversationInfo(SHORTCUT_ID_3, LOCUS_ID_3, CONTACT_URI_3, + PHONE_NUMBER_3, NOTIFICATION_CHANNEL_ID_3); + mConversationStore.addOrUpdate(in1); + mConversationStore.addOrUpdate(in2); + mConversationStore.addOrUpdate(in3); + + long futuresExecuted = mMockScheduledExecutorService.fastForwardTime( + 3L * DateUtils.MINUTE_IN_MILLIS); + assertEquals(1, futuresExecuted); + + mMockScheduledExecutorService.resetTimeElapsedMillis(); + + // During restoration, we want to confirm that this conversation was removed. + mConversationStore.deleteConversation(SHORTCUT_ID_3); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + + mTestContactQueryHelper.setQueryResult(true, true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2); + + resetConversationStore(); + ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID); + ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + ConversationInfo out3 = mConversationStore.getConversation(SHORTCUT_ID_3); + mConversationStore.deleteConversation(SHORTCUT_ID); + mConversationStore.deleteConversation(SHORTCUT_ID_2); + mConversationStore.deleteConversation(SHORTCUT_ID_3); + assertEquals(in1, out1); + assertEquals(in2, out2); + assertNull(out3); + } + + @Test + public void testDelayedDiskWrites() { + ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2, + PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2); + ConversationInfo in3 = buildConversationInfo(SHORTCUT_ID_3, LOCUS_ID_3, CONTACT_URI_3, + PHONE_NUMBER_3, NOTIFICATION_CHANNEL_ID_3); + + mConversationStore.addOrUpdate(in1); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + mMockScheduledExecutorService.resetTimeElapsedMillis(); + + // Should not see second conversation on disk because of disk write delay has not been + // reached. + mConversationStore.addOrUpdate(in2); + mMockScheduledExecutorService.fastForwardTime(DateUtils.MINUTE_IN_MILLIS); + + mTestContactQueryHelper.setQueryResult(true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER); + + resetConversationStore(); + ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID); + ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + assertEquals(in1, out1); + assertNull(out2); + + mConversationStore.addOrUpdate(in2); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + mMockScheduledExecutorService.resetTimeElapsedMillis(); + + mConversationStore.addOrUpdate(in3); + mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS); + + mTestContactQueryHelper.reset(); + mTestContactQueryHelper.setQueryResult(true, true, true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2, PHONE_NUMBER_3); + + resetConversationStore(); + out1 = mConversationStore.getConversation(SHORTCUT_ID); + out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + ConversationInfo out3 = mConversationStore.getConversation(SHORTCUT_ID_3); + assertEquals(in1, out1); + assertEquals(in2, out2); + assertEquals(in3, out3); + } + + @Test + public void testMimicDevicePowerOff() { + + // Even without fast forwarding time with our mock ScheduledExecutorService, we should + // see the conversations immediately saved to disk. + ConversationInfo in1 = buildConversationInfo(SHORTCUT_ID, LOCUS_ID, CONTACT_URI, + PHONE_NUMBER, NOTIFICATION_CHANNEL_ID); + ConversationInfo in2 = buildConversationInfo(SHORTCUT_ID_2, LOCUS_ID_2, CONTACT_URI_2, + PHONE_NUMBER_2, NOTIFICATION_CHANNEL_ID_2); + + mConversationStore.addOrUpdate(in1); + mConversationStore.addOrUpdate(in2); + mConversationStore.saveConversationsToDisk(); + + // Ensure that futures were cancelled and the immediate flush occurred. + assertEquals(0, mMockScheduledExecutorService.getFutures().size()); + + // Expect to see 2 executes: loadConversationFromDisk and saveConversationsToDisk. + // loadConversationFromDisk gets called each time we call #resetConversationStore(). + assertEquals(2, mMockScheduledExecutorService.getExecutes().size()); + + mTestContactQueryHelper.setQueryResult(true, true); + mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2); + + resetConversationStore(); + ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID); + ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2); + assertEquals(in1, out1); + assertEquals(in2, out2); + } + + private void resetConversationStore() { + mFile.mkdir(); + mMockScheduledExecutorService = new MockScheduledExecutorService(); + mConversationStore = new ConversationStore(mFile, mMockScheduledExecutorService, + mTestContactQueryHelper); + mConversationStore.loadConversationsFromDisk(); + } + private static ConversationInfo buildConversationInfo(String shortcutId) { return buildConversationInfo(shortcutId, null, null, null, null); } @@ -171,4 +326,54 @@ public final class ConversationStoreTest { .setBubbled(true) .build(); } + + private static class TestContactQueryHelper extends ContactsQueryHelper { + + private int mQueryCalls; + private boolean[] mQueryResult; + + private int mPhoneNumberCalls; + private String[] mPhoneNumberResult; + + TestContactQueryHelper(Context context) { + super(context); + + mQueryCalls = 0; + mPhoneNumberCalls = 0; + } + + private void setQueryResult(boolean... values) { + mQueryResult = values; + } + + private void setPhoneNumberResult(String... values) { + mPhoneNumberResult = values; + } + + private void reset() { + mQueryCalls = 0; + mQueryResult = null; + mPhoneNumberCalls = 0; + mPhoneNumberResult = null; + } + + @Override + boolean query(String contactUri) { + if (mQueryResult != null && mQueryCalls < mQueryResult.length) { + return mQueryResult[mQueryCalls++]; + } + mQueryCalls++; + return false; + } + + @Override + @Nullable + String getPhoneNumber() { + if (mPhoneNumberResult != null && mPhoneNumberCalls < mPhoneNumberResult.length) { + return mPhoneNumberResult[mPhoneNumberCalls++]; + } + mPhoneNumberCalls++; + return null; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java b/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java new file mode 100644 index 000000000000..8b8ba1247a4b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/people/data/MockScheduledExecutorService.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.people.data; + +import com.android.internal.util.Preconditions; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Mock implementation of ScheduledExecutorService for testing. All commands will run + * synchronously. Commands passed to {@link #submit(Runnable)} and {@link #execute(Runnable)} will + * run immediately. Commands scheduled via {@link #schedule(Runnable, long, TimeUnit)} will run + * after calling {@link #fastForwardTime(long)}. + */ +class MockScheduledExecutorService implements ScheduledExecutorService { + + private final List<Runnable> mExecutes = new ArrayList<>(); + private final List<MockScheduledFuture<?>> mFutures = new ArrayList<>(); + private long mTimeElapsedMillis = 0; + + /** + * Advances fake time, runs all the commands for which the delay has expired. + */ + long fastForwardTime(long millis) { + mTimeElapsedMillis += millis; + ImmutableList<MockScheduledFuture<?>> futuresCopy = ImmutableList.copyOf(mFutures); + mFutures.clear(); + long totalExecuted = 0; + for (MockScheduledFuture<?> future : futuresCopy) { + if (future.getDelay() < mTimeElapsedMillis) { + future.getCommand().run(); + mExecutes.add(future.getCommand()); + totalExecuted += 1; + } else { + mFutures.add(future); + } + } + return totalExecuted; + } + + List<Runnable> getExecutes() { + return mExecutes; + } + + List<MockScheduledFuture<?>> getFutures() { + return mFutures; + } + + void resetTimeElapsedMillis() { + mTimeElapsedMillis = 0; + } + + /** + * Fakes a schedule execution of {@link Runnable}. The command will be executed by an explicit + * call to {@link #fastForwardTime(long)}. + */ + @Override + public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { + Preconditions.checkState(unit == TimeUnit.MILLISECONDS); + MockScheduledFuture<?> future = new MockScheduledFuture<>(command, delay, unit); + mFutures.add(future); + return future; + } + + @Override + public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, + TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, + long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public void shutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public List<Runnable> shutdownNow() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Callable<T> task) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Runnable task, T result) { + throw new UnsupportedOperationException(); + } + + @Override + public Future<?> submit(Runnable command) { + mExecutes.add(command); + MockScheduledFuture<?> future = new MockScheduledFuture<>(command, 0, + TimeUnit.MILLISECONDS); + future.getCommand().run(); + return future; + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) + throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, + TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks) + throws ExecutionException, InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) + throws ExecutionException, InterruptedException, TimeoutException { + throw new UnsupportedOperationException(); + } + + @Override + public void execute(Runnable command) { + mExecutes.add(command); + command.run(); + } + + class MockScheduledFuture<V> implements ScheduledFuture<V> { + + private final Runnable mCommand; + private final long mDelay; + private boolean mCancelled = false; + + MockScheduledFuture(Runnable command, long delay, TimeUnit timeUnit) { + mCommand = command; + mDelay = delay; + } + + public long getDelay() { + return mDelay; + } + + public Runnable getCommand() { + return mCommand; + } + + @Override + public long getDelay(TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public int compareTo(Delayed o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + mCancelled = true; + return mFutures.remove(this); + } + + @Override + public boolean isCancelled() { + return mCancelled; + } + + @Override + public boolean isDone() { + return !mFutures.contains(this); + } + + @Override + public V get() throws ExecutionException, InterruptedException { + return null; + } + + @Override + public V get(long timeout, TimeUnit unit) + throws ExecutionException, InterruptedException, TimeoutException { + return null; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java index ec4789ad0cdf..1ddc21e4ea4d 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java @@ -20,15 +20,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.content.LocusId; import android.content.pm.ShortcutInfo; import android.net.Uri; +import androidx.test.InstrumentationRegistry; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.File; import java.util.List; @RunWith(JUnit4.class) @@ -52,8 +56,12 @@ public final class PackageDataTest { @Before public void setUp() { + Context ctx = InstrumentationRegistry.getContext(); + File testDir = new File(ctx.getCacheDir(), "testdir"); + testDir.mkdir(); mPackageData = new PackageData( - PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp); + PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp, + new MockScheduledExecutorService(), testDir, new ContactsQueryHelper(ctx)); ConversationInfo conversationInfo = new ConversationInfo.Builder() .setShortcutId(SHORTCUT_ID) .setLocusId(LOCUS_ID) diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java index e4248a04878d..b273578ada3a 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java @@ -29,8 +29,11 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; +import android.content.Context; import android.content.LocusId; +import androidx.test.InstrumentationRegistry; + import com.android.server.LocalServices; import org.junit.After; @@ -41,10 +44,12 @@ import org.junit.runners.JUnit4; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Predicate; @RunWith(JUnit4.class) @@ -69,7 +74,13 @@ public final class UsageStatsQueryHelperTest { addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal); - mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false); + Context ctx = InstrumentationRegistry.getContext(); + File testDir = new File(ctx.getCacheDir(), "testdir"); + ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService(); + ContactsQueryHelper helper = new ContactsQueryHelper(ctx); + + mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false, + scheduledExecutorService, testDir, helper); mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder() .setShortcutId(SHORTCUT_ID) .setNotificationChannelId(NOTIFICATION_CHANNEL_ID) @@ -175,7 +186,7 @@ public final class UsageStatsQueryHelperTest { assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2)); } - private void addUsageEvents(UsageEvents.Event ... events) { + private void addUsageEvents(UsageEvents.Event... events) { UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{}); when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(), anyBoolean(), anyBoolean())).thenReturn(usageEvents); @@ -228,6 +239,12 @@ public final class UsageStatsQueryHelperTest { private ConversationInfo mConversationInfo; + TestConversationStore(File packageDir, + ScheduledExecutorService scheduledExecutorService, + ContactsQueryHelper helper) { + super(packageDir, scheduledExecutorService, helper); + } + @Override @Nullable ConversationInfo getConversation(@Nullable String shortcutId) { @@ -237,13 +254,18 @@ public final class UsageStatsQueryHelperTest { private static class TestPackageData extends PackageData { - private final TestConversationStore mConversationStore = new TestConversationStore(); + private final TestConversationStore mConversationStore; private final TestEventStore mEventStore = new TestEventStore(); TestPackageData(@NonNull String packageName, @UserIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, - @NonNull Predicate<String> isDefaultSmsAppPredicate) { - super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate); + @NonNull Predicate<String> isDefaultSmsAppPredicate, + @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir, + @NonNull ContactsQueryHelper helper) { + super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate, + scheduledExecutorService, rootDir, helper); + mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService, + helper); } @Override diff --git a/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java b/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java new file mode 100644 index 000000000000..a13823441665 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/PreRebootLoggerTest.java @@ -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. + */ + +package com.android.server.power; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.provider.Settings; +import android.test.mock.MockContentResolver; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.util.test.FakeSettingsProvider; + +import com.google.common.io.Files; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; + +/** + * Tests for {@link PreRebootLogger} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PreRebootLoggerTest { + @Mock Context mContext; + private MockContentResolver mContentResolver; + private File mDumpDir; + + @BeforeClass + public static void setupOnce() { + FakeSettingsProvider.clearSettingsProvider(); + } + + @AfterClass + public static void tearDownOnce() { + FakeSettingsProvider.clearSettingsProvider(); + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContentResolver = new MockContentResolver(getInstrumentation().getTargetContext()); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + + mDumpDir = Files.createTempDir(); + mDumpDir.mkdir(); + mDumpDir.deleteOnExit(); + } + + @Test + public void log_adbEnabled_dumpsInformationProperly() { + Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1); + + PreRebootLogger.log(mContext, mDumpDir); + + assertThat(mDumpDir.list()).asList().containsExactly("system", "package", "rollback"); + } + + @Test + public void log_adbDisabled_wipesDumpedInformation() { + Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1); + PreRebootLogger.log(mContext, mDumpDir); + Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 0); + + PreRebootLogger.log(mContext, mDumpDir); + + assertThat(mDumpDir.listFiles()).isEmpty(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java new file mode 100644 index 000000000000..c1a1d5ecd3c8 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.server.wm.testing.Assert.assertThrows; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.res.Resources; +import android.platform.test.annotations.Presubmit; + +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Test; + +@Presubmit +public class DisplayAreaProviderTest { + + @Test + public void testFromResources_emptyProvider() { + Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider("")), + Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class)); + } + + @Test + public void testFromResources_nullProvider() { + Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider(null)), + Matchers.instanceOf(DisplayAreaPolicy.Default.Provider.class)); + } + + @Test + public void testFromResources_customProvider() { + Assert.assertThat(DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider( + TestProvider.class.getName())), Matchers.instanceOf(TestProvider.class)); + } + + @Test + public void testFromResources_badProvider_notImplementingProviderInterface() { + assertThrows(IllegalStateException.class, () -> { + DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider( + Object.class.getName())); + }); + } + + @Test + public void testFromResources_badProvider_doesntExist() { + assertThrows(IllegalStateException.class, () -> { + DisplayAreaPolicy.Provider.fromResources(resourcesWithProvider( + "com.android.wmtests.nonexistent.Provider")); + }); + } + + private static Resources resourcesWithProvider(String provider) { + Resources mock = mock(Resources.class); + when(mock.getString( + com.android.internal.R.string.config_deviceSpecificDisplayAreaPolicyProvider)) + .thenReturn(provider); + return mock; + } + + static class TestProvider implements DisplayAreaPolicy.Provider { + + @Override + public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content, + DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer, + DisplayContent.TaskContainers taskContainers) { + throw new RuntimeException("test stub"); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index e71225579989..eae007d3a767 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -29,12 +29,13 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; import android.platform.test.annotations.Presubmit; import android.util.IntArray; @@ -122,13 +123,13 @@ public class InsetsPolicyTest extends WindowTestsBase { // TODO: adjust this test if we pretend to the app that it's still able to control it. @Test public void testControlsForDispatch_forceStatusBarVisible() { - addWindow(TYPE_STATUS_BAR, "topBar").mAttrs.privateFlags |= + addWindow(TYPE_STATUS_BAR, "statusBar").mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; addWindow(TYPE_NAVIGATION_BAR, "navBar"); final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); - // The app must not control the top bar. + // The app must not control the status bar. assertNotNull(controls); assertEquals(1, controls.length); } @@ -137,6 +138,7 @@ public class InsetsPolicyTest extends WindowTestsBase { public void testControlsForDispatch_statusBarForceShowNavigation() { addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |= PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; + addWindow(TYPE_STATUS_BAR, "statusBar"); addWindow(TYPE_NAVIGATION_BAR, "navBar"); final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); @@ -169,7 +171,8 @@ public class InsetsPolicyTest extends WindowTestsBase { .getControllableInsetProvider().getSource().setVisible(false); final WindowState app = addWindow(TYPE_APPLICATION, "app"); - final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); + doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient( IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR})); @@ -184,7 +187,7 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test - public void testShowTransientBars_topCanBeTransient_appGetsTopFakeControl() { + public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() { // Adding app window before setting source visibility is to prevent the visibility from // being cleared by InsetsSourceProvider.updateVisibility. final WindowState app = addWindow(TYPE_APPLICATION, "app"); @@ -194,14 +197,15 @@ public class InsetsPolicyTest extends WindowTestsBase { addWindow(TYPE_NAVIGATION_BAR, "navBar") .getControllableInsetProvider().getSource().setVisible(true); - final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); + doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient( IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR})); final InsetsSourceControl[] controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(app); - // The app must get the fake control of the top bar, and must get the real control of the + // The app must get the fake control of the status bar, and must get the real control of the // navigation bar. assertEquals(2, controls.length); for (int i = controls.length - 1; i >= 0; i--) { @@ -222,7 +226,8 @@ public class InsetsPolicyTest extends WindowTestsBase { .getControllableInsetProvider().getSource().setVisible(false); final WindowState app = addWindow(TYPE_APPLICATION, "app"); - final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); + doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient( IntArray.wrap(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR})); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 39cdd2cb907e..5cf1fbbacaf4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -20,7 +20,9 @@ import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; @@ -33,14 +35,14 @@ import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.test.InsetsModeSession; +import androidx.test.filters.FlakyTest; +import androidx.test.filters.SmallTest; + import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; -import androidx.test.filters.FlakyTest; -import androidx.test.filters.SmallTest; - @SmallTest @FlakyTest(detail = "Promote to pre-submit once confirmed stable.") @Presubmit @@ -88,6 +90,10 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); + + // IME cannot be the IME target. + ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null); getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null); @@ -98,6 +104,10 @@ public class InsetsStateControllerTest extends WindowTestsBase { public void testImeForDispatch() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); + + // IME cannot be the IME target. + ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; + InsetsSourceProvider statusBarProvider = getController().getSourceProvider(ITYPE_STATUS_BAR); statusBarProvider.setWindow(statusBar, null, ((displayFrames, windowState, rect) -> diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index f7aa3cc9e52c..0312df6d34fc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -34,14 +34,21 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; +import android.app.PictureInPictureParams; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; @@ -49,6 +56,8 @@ import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; +import android.content.pm.ActivityInfo; +import android.util.Rational; import android.view.Display; import android.view.ITaskOrganizer; import android.view.IWindowContainer; @@ -384,11 +393,19 @@ public class TaskOrganizerTests extends WindowTestsBase { RunningTaskInfo info2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( mDisplayContent.mDisplayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + final int initialRootTaskCount = mWm.mAtmService.mTaskOrganizerController.getRootTasks( + mDisplayContent.mDisplayId, null /* activityTypes */).size(); + final ActivityStack stack = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent); final ActivityStack stack2 = createTaskStackOnDisplay( WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent); + // Check getRootTasks works + List<RunningTaskInfo> roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks( + mDisplayContent.mDisplayId, null /* activityTypes */); + assertEquals(initialRootTaskCount + 2, roots.size()); + lastReportedTiles.clear(); WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */); @@ -415,11 +432,18 @@ public class TaskOrganizerTests extends WindowTestsBase { // Check the getChildren call List<RunningTaskInfo> children = - mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token); + mWm.mAtmService.mTaskOrganizerController.getChildTasks(info1.token, + null /* activityTypes */); assertEquals(2, children.size()); - children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token); + children = mWm.mAtmService.mTaskOrganizerController.getChildTasks(info2.token, + null /* activityTypes */); assertEquals(0, children.size()); + // Check that getRootTasks doesn't include children of tiles + roots = mWm.mAtmService.mTaskOrganizerController.getRootTasks(mDisplayContent.mDisplayId, + null /* activityTypes */); + assertEquals(initialRootTaskCount, roots.size()); + lastReportedTiles.clear(); wct = new WindowContainerTransaction(); wct.reorder(stack2.mRemoteToken, true /* onTop */); @@ -483,4 +507,76 @@ public class TaskOrganizerTests extends WindowTestsBase { verify(transactionListener) .transactionReady(anyInt(), any()); } + + class StubOrganizer extends ITaskOrganizer.Stub { + RunningTaskInfo mInfo; + + @Override + public void taskAppeared(RunningTaskInfo info) { + mInfo = info; + } + @Override + public void taskVanished(IWindowContainer wc) { + } + @Override + public void transactionReady(int id, SurfaceControl.Transaction t) { + } + @Override + public void onTaskInfoChanged(RunningTaskInfo info) { + } + }; + + private ActivityRecord makePipableActivity() { + final ActivityRecord record = createActivityRecord(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + record.info.flags |= ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; + spyOn(record); + doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean()); + return record; + } + + @Test + public void testEnterPipParams() { + final StubOrganizer o = new StubOrganizer(); + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED); + final ActivityRecord record = makePipableActivity(); + + final PictureInPictureParams p = + new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build(); + assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p)); + waitUntilHandlersIdle(); + assertNotNull(o.mInfo); + assertNotNull(o.mInfo.pictureInPictureParams); + } + + @Test + public void testChangePipParams() { + class ChangeSavingOrganizer extends StubOrganizer { + RunningTaskInfo mChangedInfo; + @Override + public void onTaskInfoChanged(RunningTaskInfo info) { + mChangedInfo = info; + } + } + ChangeSavingOrganizer o = new ChangeSavingOrganizer(); + mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o, WINDOWING_MODE_PINNED); + + final ActivityRecord record = makePipableActivity(); + final PictureInPictureParams p = + new PictureInPictureParams.Builder().setAspectRatio(new Rational(1, 2)).build(); + assertTrue(mWm.mAtmService.enterPictureInPictureMode(record.token, p)); + waitUntilHandlersIdle(); + assertNotNull(o.mInfo); + assertNotNull(o.mInfo.pictureInPictureParams); + + final PictureInPictureParams p2 = + new PictureInPictureParams.Builder().setAspectRatio(new Rational(3, 4)).build(); + mWm.mAtmService.setPictureInPictureParams(record.token, p2); + waitUntilHandlersIdle(); + assertNotNull(o.mChangedInfo); + assertNotNull(o.mChangedInfo.pictureInPictureParams); + final Rational ratio = o.mChangedInfo.pictureInPictureParams.getAspectRatioRational(); + assertEquals(3, ratio.getNumerator()); + assertEquals(4, ratio.getDenominator()); + } } diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index f54f8d1f5832..ec99f36f6e70 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -573,6 +573,7 @@ public final class Call { /** * Indicates that the call is an adhoc conference call. This property can be set for both * incoming and outgoing calls. + * @hide */ public static final int PROPERTY_IS_ADHOC_CONFERENCE = 0x00002000; diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index 6b0845f5d12b..56acdff530eb 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -174,6 +174,7 @@ public abstract class Conference extends Conferenceable { /** * Returns whether this conference is requesting that the system play a ringback tone * on its behalf. + * @hide */ public final boolean isRingbackRequested() { return mRingbackRequested; @@ -324,6 +325,7 @@ public abstract class Conference extends Conferenceable { * the default dialer's {@link InCallService}. * * @param videoState The video state in which to answer the connection. + * @hide */ public void onAnswer(int videoState) {} @@ -343,6 +345,7 @@ public abstract class Conference extends Conferenceable { * a request to reject. * For managed {@link ConnectionService}s, this will be called when the user rejects a call via * the default dialer's {@link InCallService}. + * @hide */ public void onReject() {} @@ -362,6 +365,7 @@ public abstract class Conference extends Conferenceable { /** * Sets state to be ringing. + * @hide */ public final void setRinging() { setState(Connection.STATE_RINGING); @@ -487,6 +491,7 @@ public abstract class Conference extends Conferenceable { * that do not play a ringback tone themselves in the conference's audio stream. * * @param ringback Whether the ringback tone is to be played. + * @hide */ public final void setRingbackRequested(boolean ringback) { if (mRingbackRequested != ringback) { @@ -736,6 +741,7 @@ public abstract class Conference extends Conferenceable { * * @param disconnectCause The disconnect cause, ({@see android.telecomm.DisconnectCause}). * @return A {@code Conference} which indicates failure. + * @hide */ public @NonNull static Conference createFailedConference( @NonNull DisconnectCause disconnectCause, @NonNull PhoneAccountHandle phoneAccount) { diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 72c66d20548a..8049459cf3f4 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -501,7 +501,7 @@ public abstract class Connection extends Conferenceable { * Set by the framework to indicate that it is an adhoc conference call. * <p> * This is used for Outgoing and incoming conference calls. - * + * @hide */ public static final int PROPERTY_IS_ADHOC_CONFERENCE = 1 << 12; diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index bf4dee2c1f04..a28cc4f69155 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1875,8 +1875,8 @@ public class TelecomManager { * {@link #registerPhoneAccount}. * @param extras A bundle that will be passed through to * {@link ConnectionService#onCreateIncomingConference}. + * @hide */ - public void addNewIncomingConference(@NonNull PhoneAccountHandle phoneAccount, @NonNull Bundle extras) { try { @@ -2115,6 +2115,7 @@ public class TelecomManager { * * @param participants List of participants to start conference with * @param extras Bundle of extras to use with the call + * @hide */ @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void startConference(@NonNull List<Uri> participants, diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 553bcff931d2..e97cfaf0afa6 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -123,6 +123,14 @@ public final class CarrierAppUtils { return userContext.getContentResolver(); } + private static boolean isUpdatedSystemApp(ApplicationInfo ai) { + if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + return true; + } + + return false; + } + /** * Disable carrier apps until they are privileged * Must be public b/c framework unit tests can't access package-private methods. @@ -137,7 +145,7 @@ public final class CarrierAppUtils { PackageManager packageManager = context.getPackageManager(); PermissionManager permissionManager = (PermissionManager) context.getSystemService(Context.PERMISSION_SERVICE); - List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper( + List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper( userId, systemCarrierAppsDisabledUntilUsed, context); if (candidates == null || candidates.isEmpty()) { return; @@ -176,7 +184,7 @@ public final class CarrierAppUtils { if (hasPrivileges) { // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (enabledSetting + if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED @@ -230,7 +238,7 @@ public final class CarrierAppUtils { } else { // No carrier privileges // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (enabledSetting + if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { Log.i(TAG, "Update state(" + packageName @@ -361,29 +369,6 @@ public final class CarrierAppUtils { return apps; } - private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper( - int userId, ArraySet<String> systemCarrierAppsDisabledUntilUsed, Context context) { - if (systemCarrierAppsDisabledUntilUsed == null) { - return null; - } - - int size = systemCarrierAppsDisabledUntilUsed.size(); - if (size == 0) { - return null; - } - - List<ApplicationInfo> apps = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); - ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp(userId, packageName, context); - if (ai != null) { - apps.add(ai); - } - } - return apps; - } - private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper( int userId, ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed, Context context) { @@ -395,11 +380,11 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp( + getApplicationInfoIfSystemApp( userId, associatedAppPackages.get(j), context); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. - if (ai != null) { + if (ai != null && !isUpdatedSystemApp(ai)) { List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage); if (appList == null) { appList = new ArrayList<>(); @@ -413,26 +398,6 @@ public final class CarrierAppUtils { } @Nullable - private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp( - int userId, String packageName, Context context) { - try { - ApplicationInfo ai = context.createContextAsUser(UserHandle.of(userId), 0) - .getPackageManager() - .getApplicationInfo(packageName, - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS - | PackageManager.MATCH_SYSTEM_ONLY - | PackageManager.MATCH_FACTORY_ONLY); - if (ai != null) { - return ai; - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Could not reach PackageManager", e); - } - return null; - } - - @Nullable private static ApplicationInfo getApplicationInfoIfSystemApp( int userId, String packageName, Context context) { try { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 945c8888f402..ebb53c50ca98 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1084,6 +1084,7 @@ public class CarrierConfigManager { /** * Determines whether adhoc conference calls are supported by a carrier. When {@code true}, * adhoc conference calling is supported, {@code false otherwise}. + * @hide */ public static final String KEY_SUPPORT_ADHOC_CONFERENCE_CALLS_BOOL = "support_adhoc_conference_calls_bool"; diff --git a/tests/BootImageProfileTest/OWNERS b/tests/BootImageProfileTest/OWNERS new file mode 100644 index 000000000000..657b3f2add2e --- /dev/null +++ b/tests/BootImageProfileTest/OWNERS @@ -0,0 +1,4 @@ +mathieuc@google.com +calin@google.com +yawanng@google.com +sehr@google.com diff --git a/tests/PlatformCompatGating/Android.bp b/tests/PlatformCompatGating/Android.bp index 74dfde848191..342c47de755a 100644 --- a/tests/PlatformCompatGating/Android.bp +++ b/tests/PlatformCompatGating/Android.bp @@ -18,6 +18,7 @@ android_test { name: "PlatformCompatGating", // Only compile source java files in this apk. srcs: ["src/**/*.java"], + test_suites: ["device-tests"], static_libs: [ "junit", "androidx.test.runner", diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java index 2c2e8282ff51..0393248c34d2 100644 --- a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java +++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java @@ -16,22 +16,98 @@ package com.android.tests.rollback.host; +import static org.junit.Assert.assertTrue; +import static org.testng.Assert.assertThrows; + +import com.android.tradefed.device.LogcatReceiver; +import com.android.tradefed.result.InputStreamSource; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + /** * Runs the network rollback tests. */ @RunWith(DeviceJUnit4ClassRunner.class) public class NetworkStagedRollbackTest extends BaseHostJUnit4Test { /** + * Runs the given phase of a test by calling into the device. + * Throws an exception if the test phase fails. + * <p> + * For example, <code>runPhase("testApkOnlyEnableRollback");</code> + */ + private void runPhase(String phase) throws Exception { + assertTrue(runDeviceTests("com.android.tests.rollback", + "com.android.tests.rollback.NetworkStagedRollbackTest", + phase)); + } + + private static final String REASON_EXPLICIT_HEALTH_CHECK = "REASON_EXPLICIT_HEALTH_CHECK"; + + private static final String ROLLBACK_INITIATE = "ROLLBACK_INITIATE"; + private static final String ROLLBACK_BOOT_TRIGGERED = "ROLLBACK_BOOT_TRIGGERED"; + + private LogcatReceiver mReceiver; + + @Before + public void setUp() throws Exception { + mReceiver = new LogcatReceiver(getDevice(), "logcat -s WatchdogRollbackLogger", + getDevice().getOptions().getMaxLogcatDataSize(), 0); + mReceiver.start(); + } + + @After + public void tearDown() throws Exception { + mReceiver.stop(); + mReceiver.clear(); + } + + /** * Tests failed network health check triggers watchdog staged rollbacks. */ @Test public void testNetworkFailedRollback() throws Exception { + try { + // Disconnect internet so we can test network health triggered rollbacks + getDevice().executeShellCommand("svc wifi disable"); + getDevice().executeShellCommand("svc data disable"); + + runPhase("testNetworkFailedRollback_Phase1"); + // Reboot device to activate staged package + getDevice().reboot(); + + // Verify rollback was enabled + runPhase("testNetworkFailedRollback_Phase2"); + assertThrows(AssertionError.class, () -> runPhase("testNetworkFailedRollback_Phase3")); + + getDevice().waitForDeviceAvailable(); + // Verify rollback was executed after health check deadline + runPhase("testNetworkFailedRollback_Phase4"); + InputStreamSource logcatStream = mReceiver.getLogcatData(); + try { + List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, + REASON_EXPLICIT_HEALTH_CHECK, null)); + assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, + null, null)); + } finally { + logcatStream.close(); + } + } finally { + // Reconnect internet again so we won't break tests which assume internet available + getDevice().executeShellCommand("svc wifi enable"); + getDevice().executeShellCommand("svc data enable"); + } } /** @@ -40,4 +116,57 @@ public class NetworkStagedRollbackTest extends BaseHostJUnit4Test { @Test public void testNetworkPassedDoesNotRollback() throws Exception { } + + /** + * Returns a list of all Watchdog logging events which have occurred. + */ + private List<String> getWatchdogLoggingEvents(InputStreamSource inputStreamSource) + throws Exception { + List<String> watchdogEvents = new ArrayList<>(); + InputStream inputStream = inputStreamSource.createInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("Watchdog event occurred")) { + watchdogEvents.add(line); + } + } + return watchdogEvents; + } + + /** + * Returns whether a Watchdog event has occurred that matches the given criteria. + * + * Check the value of all non-null parameters against the list of Watchdog events that have + * occurred, and return {@code true} if an event exists which matches all criteria. + */ + private boolean watchdogEventOccurred(List<String> loggingEvents, + String type, String logPackage, + String rollbackReason, String failedPackageName) throws Exception { + List<String> eventCriteria = new ArrayList<>(); + if (type != null) { + eventCriteria.add("type: " + type); + } + if (logPackage != null) { + eventCriteria.add("logPackage: " + logPackage); + } + if (rollbackReason != null) { + eventCriteria.add("rollbackReason: " + rollbackReason); + } + if (failedPackageName != null) { + eventCriteria.add("failedPackageName: " + failedPackageName); + } + for (String loggingEvent: loggingEvents) { + boolean matchesCriteria = true; + for (String criterion: eventCriteria) { + if (!loggingEvent.contains(criterion)) { + matchesCriteria = false; + } + } + if (matchesCriteria) { + return true; + } + } + return false; + } } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java index 04004d6abb5a..e5c8a685813f 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java @@ -16,9 +16,143 @@ package com.android.tests.rollback; +import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat; +import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage; + +import android.Manifest; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.rollback.RollbackManager; +import android.os.ParcelFileDescriptor; +import android.provider.DeviceConfig; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.cts.install.lib.Install; +import com.android.cts.install.lib.InstallUtils; +import com.android.cts.install.lib.TestApp; +import com.android.cts.rollback.lib.RollbackUtils; + +import libcore.io.IoUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.File; +import java.util.concurrent.TimeUnit; + @RunWith(JUnit4.class) public class NetworkStagedRollbackTest { + private static final String NETWORK_STACK_CONNECTOR_CLASS = + "android.net.INetworkStackConnector"; + private static final String PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS = + "watchdog_request_timeout_millis"; + + private static final TestApp NETWORK_STACK = new TestApp("NetworkStack", + getNetworkStackPackageName(), -1, false, findNetworkStackApk()); + + private static File findNetworkStackApk() { + final File apk = new File("/system/priv-app/NetworkStack/NetworkStack.apk"); + if (apk.isFile()) { + return apk; + } + return new File("/system/priv-app/NetworkStackNext/NetworkStackNext.apk"); + } + + /** + * Adopts common shell permissions needed for rollback tests. + */ + @Before + public void adoptShellPermissions() { + InstallUtils.adoptShellPermissionIdentity( + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.TEST_MANAGE_ROLLBACKS, + Manifest.permission.FORCE_STOP_PACKAGES, + Manifest.permission.WRITE_DEVICE_CONFIG); + } + + /** + * Drops shell permissions needed for rollback tests. + */ + @After + public void dropShellPermissions() { + InstallUtils.dropShellPermissionIdentity(); + } + + @Test + public void testNetworkFailedRollback_Phase1() throws Exception { + // Remove available rollbacks and uninstall NetworkStack on /data/ + RollbackManager rm = RollbackUtils.getRollbackManager(); + String networkStack = getNetworkStackPackageName(); + + rm.expireRollbackForPackage(networkStack); + uninstallNetworkStackPackage(); + + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + networkStack)).isNull(); + + // Reduce health check deadline + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS, + Integer.toString(120000), false); + // Simulate re-installation of new NetworkStack with rollbacks enabled + installNetworkStackPackage(); + } + + @Test + public void testNetworkFailedRollback_Phase2() throws Exception { + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + getNetworkStackPackageName())).isNotNull(); + + // Sleep for < health check deadline + Thread.sleep(TimeUnit.SECONDS.toMillis(5)); + // Verify rollback was not executed before health check deadline + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())).isNull(); + } + + @Test + public void testNetworkFailedRollback_Phase3() throws Exception { + // Sleep for > health check deadline (120s to trigger rollback + 120s to reboot) + // The device is expected to reboot during sleeping. This device method will fail and + // the host will catch the assertion. If reboot doesn't happen, the host will fail the + // assertion. + Thread.sleep(TimeUnit.SECONDS.toMillis(240)); + } + + @Test + public void testNetworkFailedRollback_Phase4() throws Exception { + RollbackManager rm = RollbackUtils.getRollbackManager(); + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())).isNotNull(); + } + + private static String getNetworkStackPackageName() { + Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS); + ComponentName comp = intent.resolveSystemService( + InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(), 0); + return comp.getPackageName(); + } + + private static void installNetworkStackPackage() throws Exception { + Install.single(NETWORK_STACK).setStaged().setEnableRollback() + .addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit(); + } + + private static void uninstallNetworkStackPackage() { + // Uninstall the package as a privileged user so we won't fail due to permission. + runShellCommand("pm uninstall " + getNetworkStackPackageName()); + } + + private static void runShellCommand(String cmd) { + ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation() + .executeShellCommand(cmd); + IoUtils.closeQuietly(pfd); + } } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 0fdcbc52bffa..bddd93c6de36 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -274,55 +274,6 @@ public class StagedRollbackTest { TestApp.B)).isNotNull(); } - @Test - public void testNetworkFailedRollback_Phase1() throws Exception { - // Remove available rollbacks and uninstall NetworkStack on /data/ - RollbackManager rm = RollbackUtils.getRollbackManager(); - String networkStack = getNetworkStackPackageName(); - - rm.expireRollbackForPackage(networkStack); - uninstallNetworkStackPackage(); - - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - networkStack)).isNull(); - - // Reduce health check deadline - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS, - Integer.toString(120000), false); - // Simulate re-installation of new NetworkStack with rollbacks enabled - installNetworkStackPackage(); - } - - @Test - public void testNetworkFailedRollback_Phase2() throws Exception { - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - getNetworkStackPackageName())).isNotNull(); - - // Sleep for < health check deadline - Thread.sleep(TimeUnit.SECONDS.toMillis(5)); - // Verify rollback was not executed before health check deadline - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - getNetworkStackPackageName())).isNull(); - } - - @Test - public void testNetworkFailedRollback_Phase3() throws Exception { - // Sleep for > health check deadline (120s to trigger rollback + 120s to reboot) - // The device is expected to reboot during sleeping. This device method will fail and - // the host will catch the assertion. If reboot doesn't happen, the host will fail the - // assertion. - Thread.sleep(TimeUnit.SECONDS.toMillis(240)); - } - - @Test - public void testNetworkFailedRollback_Phase4() throws Exception { - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - getNetworkStackPackageName())).isNotNull(); - } - private static String getNetworkStackPackageName() { Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS); ComponentName comp = intent.resolveSystemService( diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 032f18240a55..3c5eaef86bcc 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -228,44 +228,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } /** - * Tests failed network health check triggers watchdog staged rollbacks. - */ - @Test - public void testNetworkFailedRollback() throws Exception { - try { - // Disconnect internet so we can test network health triggered rollbacks - getDevice().executeShellCommand("svc wifi disable"); - getDevice().executeShellCommand("svc data disable"); - - runPhase("testNetworkFailedRollback_Phase1"); - // Reboot device to activate staged package - getDevice().reboot(); - - // Verify rollback was enabled - runPhase("testNetworkFailedRollback_Phase2"); - assertThrows(AssertionError.class, () -> runPhase("testNetworkFailedRollback_Phase3")); - - getDevice().waitForDeviceAvailable(); - // Verify rollback was executed after health check deadline - runPhase("testNetworkFailedRollback_Phase4"); - InputStreamSource logcatStream = mReceiver.getLogcatData(); - try { - List<String> watchdogEvents = getWatchdogLoggingEvents(logcatStream); - assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null, - REASON_EXPLICIT_HEALTH_CHECK, null)); - assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_BOOT_TRIGGERED, null, - null, null)); - } finally { - logcatStream.close(); - } - } finally { - // Reconnect internet again so we won't break tests which assume internet available - getDevice().executeShellCommand("svc wifi enable"); - getDevice().executeShellCommand("svc data enable"); - } - } - - /** * Tests passed network health check does not trigger watchdog staged rollbacks. */ @Test diff --git a/tests/RollbackTest/TEST_MAPPING b/tests/RollbackTest/TEST_MAPPING index fefde5b4be12..0f4c4603f9b4 100644 --- a/tests/RollbackTest/TEST_MAPPING +++ b/tests/RollbackTest/TEST_MAPPING @@ -7,6 +7,9 @@ "name": "StagedRollbackTest" }, { + "name": "NetworkStagedRollbackTest" + }, + { "name": "MultiUserRollbackTest" } ] diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index d4e024dbe0c7..1c330e263d7e 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -272,4 +272,8 @@ interface IWifiManager boolean isScanThrottleEnabled(); Map getAllMatchingPasspointProfilesForScanResults(in List<ScanResult> scanResult); + + void setAutoWakeupEnabled(boolean enable); + + boolean isAutoWakeupEnabled(); } diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index a720236689d3..0cce23d196cf 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -951,7 +951,7 @@ public class WifiConfiguration implements Parcelable { * Indicate whether the network is trusted or not. Networks are considered trusted * if the user explicitly allowed this network connection. * This bit can be used by suggestion network, see - * {@link WifiNetworkSuggestion.Builder#setUnTrusted(boolean)} + * {@link WifiNetworkSuggestion.Builder#setUntrusted(boolean)} * @hide */ public boolean trusted; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index fb30910c17b1..b6f4490a1872 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -6163,4 +6163,50 @@ public class WifiManager { throw e.rethrowFromSystemServer(); } } + + /** + * Enable/disable wifi auto wakeup feature. + * + * <p> + * The feature is described in + * <a href="Wi-Fi Turn on automatically"> + * https://source.android.com/devices/tech/connect/wifi-infrastructure + * #turn_on_wi-fi_automatically + * </a> + * + * @param enable true to enable, false to disable. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void setAutoWakeupEnabled(boolean enable) { + try { + mService.setAutoWakeupEnabled(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the persisted Wi-Fi auto wakeup feature state. Defaults to false, unless changed by the + * user via Settings. + * + * <p> + * The feature is described in + * <a href="Wi-Fi Turn on automatically"> + * https://source.android.com/devices/tech/connect/wifi-infrastructure + * #turn_on_wi-fi_automatically + * </a> + * + * @return true to indicate that wakeup feature is enabled, false to indicate that wakeup + * feature is disabled. + */ + @RequiresPermission(ACCESS_WIFI_STATE) + public boolean isAutoWakeupEnabled() { + try { + return mService.isAutoWakeupEnabled(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/wifi/java/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java index 7cc617d61b00..bd99476afe43 100644 --- a/wifi/java/android/net/wifi/wificond/NativeScanResult.java +++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java @@ -16,14 +16,21 @@ package android.net.wifi.wificond; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; +import android.net.MacAddress; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -33,6 +40,8 @@ import java.util.List; */ @SystemApi public final class NativeScanResult implements Parcelable { + private static final String TAG = "NativeScanResult"; + /** @hide */ @VisibleForTesting public byte[] ssid; @@ -53,7 +62,7 @@ public final class NativeScanResult implements Parcelable { public long tsf; /** @hide */ @VisibleForTesting - public int capability; + @BssCapabilityBits public int capability; /** @hide */ @VisibleForTesting public boolean associated; @@ -71,14 +80,17 @@ public final class NativeScanResult implements Parcelable { } /** - * Returns raw bytes representing the MAC address (BSSID) of the AP represented by this scan - * result. + * Returns the MAC address (BSSID) of the AP represented by this scan result. * - * @return a byte array, possibly null or containing the incorrect number of bytes for a MAC - * address. + * @return a MacAddress or null on error. */ - @NonNull public byte[] getBssid() { - return bssid; + @Nullable public MacAddress getBssid() { + try { + return MacAddress.fromBytes(bssid); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument " + Arrays.toString(bssid), e); + return null; + } } /** @@ -127,31 +139,103 @@ public final class NativeScanResult implements Parcelable { return associated; } + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = {"BSS_CAPABILITY_"}, + value = {BSS_CAPABILITY_ESS, + BSS_CAPABILITY_IBSS, + BSS_CAPABILITY_CF_POLLABLE, + BSS_CAPABILITY_CF_POLL_REQUEST, + BSS_CAPABILITY_PRIVACY, + BSS_CAPABILITY_SHORT_PREAMBLE, + BSS_CAPABILITY_PBCC, + BSS_CAPABILITY_CHANNEL_AGILITY, + BSS_CAPABILITY_SPECTRUM_MANAGEMENT, + BSS_CAPABILITY_QOS, + BSS_CAPABILITY_SHORT_SLOT_TIME, + BSS_CAPABILITY_APSD, + BSS_CAPABILITY_RADIO_MANAGEMENT, + BSS_CAPABILITY_DSSS_OFDM, + BSS_CAPABILITY_DELAYED_BLOCK_ACK, + BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK + }) + public @interface BssCapabilityBits { } + + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): ESS. + */ + public static final int BSS_CAPABILITY_ESS = 0x1 << 0; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): IBSS. + */ + public static final int BSS_CAPABILITY_IBSS = 0x1 << 1; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF Pollable. + */ + public static final int BSS_CAPABILITY_CF_POLLABLE = 0x1 << 2; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): CF-Poll Request. + */ + public static final int BSS_CAPABILITY_CF_POLL_REQUEST = 0x1 << 3; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Privacy. + */ + public static final int BSS_CAPABILITY_PRIVACY = 0x1 << 4; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Preamble. + */ + public static final int BSS_CAPABILITY_SHORT_PREAMBLE = 0x1 << 5; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): PBCC. + */ + public static final int BSS_CAPABILITY_PBCC = 0x1 << 6; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Channel Agility. + */ + public static final int BSS_CAPABILITY_CHANNEL_AGILITY = 0x1 << 7; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Spectrum Management. + */ + public static final int BSS_CAPABILITY_SPECTRUM_MANAGEMENT = 0x1 << 8; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): QoS. + */ + public static final int BSS_CAPABILITY_QOS = 0x1 << 9; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Short Slot Time. + */ + public static final int BSS_CAPABILITY_SHORT_SLOT_TIME = 0x1 << 10; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): APSD. + */ + public static final int BSS_CAPABILITY_APSD = 0x1 << 11; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Radio Management. + */ + public static final int BSS_CAPABILITY_RADIO_MANAGEMENT = 0x1 << 12; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): DSSS-OFDM. + */ + public static final int BSS_CAPABILITY_DSSS_OFDM = 0x1 << 13; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Delayed Block Ack. + */ + public static final int BSS_CAPABILITY_DELAYED_BLOCK_ACK = 0x1 << 14; + /** + * BSS capability bit (see IEEE Std 802.11: 9.4.1.4): Immediate Block Ack. + */ + public static final int BSS_CAPABILITY_IMMEDIATE_BLOCK_ACK = 0x1 << 15; + /** * Returns the capabilities of the AP repseresented by this scan result as advertised in the * received probe response or beacon. * - * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4: - * Bit 0 - ESS - * Bit 1 - IBSS - * Bit 2 - CF Pollable - * Bit 3 - CF-Poll Request - * Bit 4 - Privacy - * Bit 5 - Short Preamble - * Bit 6 - PBCC - * Bit 7 - Channel Agility - * Bit 8 - Spectrum Management - * Bit 9 - QoS - * Bit 10 - Short Slot Time - * Bit 11 - APSD - * Bit 12 - Radio Measurement - * Bit 13 - DSSS-OFDM - * Bit 14 - Delayed Block Ack - * Bit 15 - Immediate Block Ack + * This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4: one + * of the {@code BSS_CAPABILITY_*} flags. * * @return a bit mask of capabilities. */ - @NonNull public int getCapabilities() { + @BssCapabilityBits public int getCapabilities() { return capability; } diff --git a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java index 916c11579075..9ad2a2769add 100644 --- a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java +++ b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java @@ -17,11 +17,13 @@ package android.net.wifi.wificond; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; +import android.net.MacAddress; import android.os.Parcel; import android.os.Parcelable; -import java.util.Arrays; +import java.util.Objects; /** * Structure providing information about clients (STAs) associated with a SoftAp. @@ -30,16 +32,21 @@ import java.util.Arrays; */ @SystemApi public final class NativeWifiClient implements Parcelable { + private final MacAddress mMacAddress; + /** - * The raw bytes of the MAC address of the client (STA) represented by this object. + * The MAC address of the client (STA) represented by this object. The MAC address may be null + * in case of an error. */ - @NonNull public final byte[] macAddress; + @Nullable public MacAddress getMacAddress() { + return mMacAddress; + } /** * Construct a native Wi-Fi client. */ - public NativeWifiClient(@NonNull byte[] macAddress) { - this.macAddress = macAddress; + public NativeWifiClient(@Nullable MacAddress macAddress) { + this.mMacAddress = macAddress; } /** override comparator */ @@ -50,13 +57,13 @@ public final class NativeWifiClient implements Parcelable { return false; } NativeWifiClient other = (NativeWifiClient) rhs; - return Arrays.equals(macAddress, other.macAddress); + return Objects.equals(mMacAddress, other.mMacAddress); } /** override hash code */ @Override public int hashCode() { - return Arrays.hashCode(macAddress); + return mMacAddress.hashCode(); } /** implement Parcelable interface */ @@ -71,7 +78,7 @@ public final class NativeWifiClient implements Parcelable { */ @Override public void writeToParcel(@NonNull Parcel out, int flags) { - out.writeByteArray(macAddress); + out.writeByteArray(mMacAddress.toByteArray()); } /** implement Parcelable interface */ @@ -79,9 +86,11 @@ public final class NativeWifiClient implements Parcelable { new Parcelable.Creator<NativeWifiClient>() { @Override public NativeWifiClient createFromParcel(Parcel in) { - byte[] macAddress = in.createByteArray(); - if (macAddress == null) { - macAddress = new byte[0]; + MacAddress macAddress; + try { + macAddress = MacAddress.fromBytes(in.createByteArray()); + } catch (IllegalArgumentException e) { + macAddress = null; } return new NativeWifiClient(macAddress); } diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java index 7a31a5afab05..61f18e0b7191 100644 --- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java +++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java @@ -41,19 +41,16 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; /** - * This class encapsulates the interface the wificond (Wi-Fi Conductor) daemon presents to the - * Wi-Fi framework. The interface is only for use by the Wi-Fi framework and access is protected - * by SELinux permissions: only the system server and wpa_supplicant can use WifiCondManager. + * This class encapsulates the interface the wificond daemon presents to the Wi-Fi framework. The + * interface is only for use by the Wi-Fi framework and access is protected by SELinux permissions. * * @hide */ @@ -371,7 +368,7 @@ public class WifiCondManager { public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) { if (mVerboseLoggingEnabled) { Log.d(TAG, "onConnectedClientsChanged called with " - + client.macAddress + " isConnected: " + isConnected); + + client.getMacAddress() + " isConnected: " + isConnected); } Binder.clearCallingIdentity(); @@ -1046,13 +1043,13 @@ public class WifiCondManager { * WifiScanner.WIFI_BAND_5_GHZ * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY * WifiScanner.WIFI_BAND_6_GHZ - * @return frequencies List of valid frequencies (MHz), or an empty list for error. + * @return frequencies vector of valid frequencies (MHz), or an empty array for error. * @throws IllegalArgumentException if band is not recognized. */ - public @NonNull List<Integer> getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) { + public @NonNull int[] getChannelsMhzForBand(@WifiAnnotations.WifiBandBasic int band) { if (mWificond == null) { Log.e(TAG, "No valid wificond scanner interface handler"); - return Collections.emptyList(); + return new int[0]; } int[] result = null; try { @@ -1076,9 +1073,9 @@ public class WifiCondManager { Log.e(TAG, "Failed to request getChannelsForBand due to remote exception"); } if (result == null) { - return Collections.emptyList(); + result = new int[0]; } - return Arrays.stream(result).boxed().collect(Collectors.toList()); + return result; } /** Helper function to look up the interface handle using name */ diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 847040ca914a..853212aafcdf 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -2391,4 +2391,14 @@ public class WifiManagerTest { assertFalse(mWifiManager.isScanThrottleEnabled()); verify(mWifiService).isScanThrottleEnabled(); } + + @Test + public void testAutoWakeup() throws Exception { + mWifiManager.setAutoWakeupEnabled(true); + verify(mWifiService).setAutoWakeupEnabled(true); + + when(mWifiService.isAutoWakeupEnabled()).thenReturn(false); + assertFalse(mWifiManager.isAutoWakeupEnabled()); + verify(mWifiService).isAutoWakeupEnabled(); + } } diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java index 32105be6ae4c..b745a341b459 100644 --- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java +++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.when; import android.app.AlarmManager; import android.app.test.TestAlarmManager; import android.content.Context; +import android.net.MacAddress; import android.net.wifi.ScanResult; import android.net.wifi.SoftApInfo; import android.net.wifi.WifiConfiguration; @@ -65,8 +66,6 @@ import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -121,7 +120,8 @@ public class WifiCondManagerTest { private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\""; private static final int[] TEST_FREQUENCIES_1 = {}; private static final int[] TEST_FREQUENCIES_2 = {2500, 5124}; - private static final byte[] TEST_RAW_MAC_BYTES = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes( + new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}); private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST = new ArrayList<byte[]>() {{ @@ -742,43 +742,11 @@ public class WifiCondManagerTest { verify(deathHandler).run(); // The handles should be cleared after death. - assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).size()); + assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).length); verify(mWificond, never()).getAvailable5gNonDFSChannels(); } /** - * Verify primitive array to list translation of channel API. - */ - @Test - public void testGetChannels() throws Exception { - int[] resultsEmpty = new int[0]; - int[] resultsSingle = new int[]{100}; - int[] resultsMore = new int[]{100, 200}; - - List<Integer> emptyList = Collections.emptyList(); - List<Integer> singleList = Arrays.asList(100); - List<Integer> moreList = Arrays.asList(100, 200); - - when(mWificond.getAvailable2gChannels()).thenReturn(null); - assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ), - emptyList); - assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ), - emptyList); - - when(mWificond.getAvailable2gChannels()).thenReturn(resultsEmpty); - assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ), - emptyList); - - when(mWificond.getAvailable2gChannels()).thenReturn(resultsSingle); - assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ), - singleList); - - when(mWificond.getAvailable2gChannels()).thenReturn(resultsMore); - assertEquals(mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_24_GHZ), - moreList); - } - - /** * sendMgmtFrame() should fail if a null callback is passed in. */ @Test |