diff options
157 files changed, 3923 insertions, 1610 deletions
diff --git a/apex/Android.bp b/apex/Android.bp index c1715a002d6d..371bd7fc9479 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -63,9 +63,9 @@ mainline_service_stubs_args = "--hide-annotation android.annotation.Hide " + "--hide InternalClasses " // com.android.* classes are okay in this interface -// Defaults for mainline module provided java_sdk_library instances. +// Defaults common to all mainline module java_sdk_library instances. java_defaults { - name: "framework-module-defaults", + name: "framework-module-common-defaults", // Additional annotations used for compiling both the implementation and the // stubs libraries. @@ -88,14 +88,6 @@ java_defaults { enabled: true, sdk_version: "module_current", }, - system: { - enabled: true, - sdk_version: "module_current", - }, - module_lib: { - enabled: true, - sdk_version: "module_current", - }, // Configure framework module specific metalava options. droiddoc_options: [mainline_stubs_args], @@ -127,6 +119,32 @@ java_defaults { sdk_version: "module_current", } +// Defaults for mainline module provided java_sdk_library instances. +java_defaults { + name: "framework-module-defaults", + defaults: ["framework-module-common-defaults"], + + system: { + enabled: true, + sdk_version: "module_current", + }, + module_lib: { + enabled: true, + sdk_version: "module_current", + }, +} + +// Defaults for mainline module system server provided java_sdk_library instances. +java_defaults { + name: "framework-system-server-module-defaults", + defaults: ["framework-module-common-defaults"], + + system_server: { + enabled: true, + sdk_version: "module_current", + }, +} + stubs_defaults { name: "framework-module-stubs-defaults-publicapi", args: mainline_framework_stubs_args, diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java index bcef8ceaa941..ecc78ce7cf34 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java @@ -219,7 +219,7 @@ public final class BlobHandle implements Parcelable { public void dump(IndentingPrintWriter fout, boolean dumpFull) { if (dumpFull) { fout.println("algo: " + algorithm); - fout.println("digest: " + (dumpFull ? encodeDigest() : safeDigest())); + fout.println("digest: " + (dumpFull ? encodeDigest(digest) : safeDigest(digest))); fout.println("label: " + label); fout.println("expiryMs: " + expiryTimeMillis); fout.println("tag: " + tag); @@ -243,19 +243,20 @@ public final class BlobHandle implements Parcelable { public String toString() { return "BlobHandle {" + "algo:" + algorithm + "," - + "digest:" + safeDigest() + "," + + "digest:" + safeDigest(digest) + "," + "label:" + label + "," + "expiryMs:" + expiryTimeMillis + "," + "tag:" + tag + "}"; } - private String safeDigest() { - final String digestStr = encodeDigest(); + /** @hide */ + public static String safeDigest(@NonNull byte[] digest) { + final String digestStr = encodeDigest(digest); return digestStr.substring(0, 2) + ".." + digestStr.substring(digestStr.length() - 2); } - private String encodeDigest() { + private static String encodeDigest(@NonNull byte[] digest) { return Base64.encodeToString(digest, Base64.NO_WRAP); } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 0a5ada282388..68c4bb675907 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -478,7 +478,9 @@ public class BlobStoreManagerService extends SystemService { ? Resources.ID_NULL : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName), leasee.descriptionResEntryName, leasee.packageName); - leaseInfos.add(new LeaseInfo(leasee.packageName, leasee.expiryTimeMillis, + final long expiryTimeMs = leasee.expiryTimeMillis == 0 + ? blobHandle.getExpiryTimeMillis() : leasee.expiryTimeMillis; + leaseInfos.add(new LeaseInfo(leasee.packageName, expiryTimeMs, descriptionResId, leasee.description)); }); blobInfos.add(new BlobInfo(blobMetadata.getBlobId(), @@ -592,6 +594,7 @@ public class BlobStoreManagerService extends SystemService { } else { blob.addOrReplaceCommitter(existingCommitter); } + Slog.d(TAG, "Error committing the blob", e); session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); } getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) 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 2b0458303a23..22d5d11e9ccb 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -28,7 +28,6 @@ import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_RDWR; import static android.system.OsConstants.SEEK_SET; -import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME; import static com.android.server.blob.BlobStoreConfig.hasSessionExpired; @@ -423,9 +422,10 @@ class BlobStoreSession extends IBlobStoreSession.Stub { mState = STATE_VERIFIED_VALID; // Commit callback will be sent once the data is persisted. } else { - if (LOGV) { - Slog.v(TAG, "Digest of the data didn't match the given BlobHandle.digest"); - } + Slog.d(TAG, "Digest of the data (" + + (mDataDigest == null ? "null" : BlobHandle.safeDigest(mDataDigest)) + + ") didn't match the given BlobHandle.digest (" + + BlobHandle.safeDigest(mBlobHandle.digest) + ")"); mState = STATE_VERIFIED_INVALID; sendCommitCallbackResult(COMMIT_RESULT_ERROR); } diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index 7cbb98e1bb4e..19f578ecfc66 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -203,6 +203,15 @@ public final class MediaParser { /** Returned by {@link #getDurationMicros()} when the duration is unknown. */ public static final int UNKNOWN_DURATION = Integer.MIN_VALUE; + /** + * For each {@link #getSeekPoints} call, returns a single {@link SeekPoint} whose {@link + * SeekPoint#timeMicros} matches the requested timestamp, and whose {@link + * SeekPoint#position} is 0. + * + * @hide + */ + public static final SeekMap DUMMY = new SeekMap(new DummyExoPlayerSeekMap()); + private final com.google.android.exoplayer2.extractor.SeekMap mExoPlayerSeekMap; private SeekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) { @@ -795,6 +804,18 @@ public final class MediaParser { */ public static final String PARAMETER_EAGERLY_EXPOSE_TRACKTYPE = "android.media.mediaparser.eagerlyExposeTrackType"; + /** + * Sets whether a dummy {@link SeekMap} should be exposed before starting extraction. {@code + * boolean} expected. Default value is {@code false}. + * + * <p>For each {@link SeekMap#getSeekPoints} call, the dummy {@link SeekMap} returns a single + * {@link SeekPoint} whose {@link SeekPoint#timeMicros} matches the requested timestamp, and + * whose {@link SeekPoint#position} is 0. + * + * @hide + */ + public static final String PARAMETER_EXPOSE_DUMMY_SEEKMAP = + "android.media.mediaparser.exposeDummySeekMap"; // Private constants. @@ -958,6 +979,7 @@ public final class MediaParser { private boolean mIncludeSupplementalData; private boolean mIgnoreTimestampOffset; private boolean mEagerlyExposeTrackType; + private boolean mExposeDummySeekMap; private String mParserName; private Extractor mExtractor; private ExtractorInput mExtractorInput; @@ -1017,6 +1039,9 @@ public final class MediaParser { if (PARAMETER_EAGERLY_EXPOSE_TRACKTYPE.equals(parameterName)) { mEagerlyExposeTrackType = (boolean) value; } + if (PARAMETER_EXPOSE_DUMMY_SEEKMAP.equals(parameterName)) { + mExposeDummySeekMap = (boolean) value; + } mParserParameters.put(parameterName, value); return this; } @@ -1078,11 +1103,10 @@ public final class MediaParser { } mExoDataReader.mInputReader = seekableInputReader; - // TODO: Apply parameters when creating extractor instances. if (mExtractor == null) { + mPendingExtractorInit = true; if (!mParserName.equals(PARSER_NAME_UNKNOWN)) { mExtractor = createExtractor(mParserName); - mExtractor.init(new ExtractorOutputAdapter()); } else { for (String parserName : mParserNamesPool) { Extractor extractor = createExtractor(parserName); @@ -1107,9 +1131,18 @@ public final class MediaParser { } if (mPendingExtractorInit) { + if (mExposeDummySeekMap) { + // We propagate the dummy seek map before initializing the extractor, in case the + // extractor initialization outputs a seek map. + mOutputConsumer.onSeekMapFound(SeekMap.DUMMY); + } mExtractor.init(new ExtractorOutputAdapter()); mPendingExtractorInit = false; + // We return after initialization to allow clients use any output information before + // starting actual extraction. + return true; } + if (isPendingSeek()) { mExtractor.seek(mPendingSeekPosition, mPendingSeekTimeMicros); removePendingSeek(); @@ -1683,6 +1716,28 @@ public final class MediaParser { } } + private static final class DummyExoPlayerSeekMap + implements com.google.android.exoplayer2.extractor.SeekMap { + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getDurationUs() { + return C.TIME_UNSET; + } + + @Override + public SeekPoints getSeekPoints(long timeUs) { + com.google.android.exoplayer2.extractor.SeekPoint seekPoint = + new com.google.android.exoplayer2.extractor.SeekPoint( + timeUs, /* position= */ 0); + return new SeekPoints(seekPoint, seekPoint); + } + } + /** Creates extractor instances. */ private interface ExtractorFactory { @@ -1923,6 +1978,7 @@ public final class MediaParser { expectedTypeByParameterName.put(PARAMETER_INCLUDE_SUPPLEMENTAL_DATA, Boolean.class); expectedTypeByParameterName.put(PARAMETER_IGNORE_TIMESTAMP_OFFSET, Boolean.class); expectedTypeByParameterName.put(PARAMETER_EAGERLY_EXPOSE_TRACKTYPE, Boolean.class); + expectedTypeByParameterName.put(PARAMETER_EXPOSE_DUMMY_SEEKMAP, Boolean.class); EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName); } } diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp index 61449763540b..7f3187949712 100644 --- a/apex/permission/service/Android.bp +++ b/apex/permission/service/Android.bp @@ -20,14 +20,26 @@ filegroup { path: "java", } -java_library { +java_sdk_library { name: "service-permission", + defaults: ["framework-system-server-module-defaults"], + visibility: [ + "//frameworks/base/services/core", + "//frameworks/base/apex/permission", + "//frameworks/base/apex/permission/testing", + "//frameworks/base/apex/permission/tests", + "//frameworks/base/services/tests/mockingservicestests", + ], + impl_library_visibility: [ + "//visibility:override", + "//frameworks/base/apex/permission/tests", + "//frameworks/base/services/tests/mockingservicestests", + "//frameworks/base/services/tests/servicestests", + ], srcs: [ ":service-permission-sources", ], - sdk_version: "module_current", libs: [ - "framework-annotations-lib", "framework-permission", ], apex_available: [ @@ -36,28 +48,3 @@ java_library { ], installable: true, } - -droidstubs { - name: "service-permission-stubs-srcs", - srcs: [ ":service-permission-sources" ], - defaults: ["service-module-stubs-srcs-defaults"], - check_api: { - last_released: { - api_file: ":service-permission.api.system-server.latest", - removed_api_file: ":service-permission-removed.api.system-server.latest", - }, - api_lint: { - new_since: ":service-permission.api.system-server.latest", - }, - }, - visibility: ["//visibility:private"], - dist: { dest: "service-permission.txt" }, -} - -java_library { - name: "service-permission-stubs", - srcs: [":service-permission-stubs-srcs"], - defaults: ["service-module-stubs-defaults"], - visibility: ["//frameworks/base/services/core"], - dist: { dest: "service-permission.jar" }, -} diff --git a/apex/permission/service/api/current.txt b/apex/permission/service/api/current.txt index c76cc3275737..d802177e249b 100644 --- a/apex/permission/service/api/current.txt +++ b/apex/permission/service/api/current.txt @@ -1,46 +1 @@ // Signature format: 2.0 -package com.android.permission.persistence { - - public interface RuntimePermissionsPersistence { - method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance(); - method public void deleteForUser(@NonNull android.os.UserHandle); - method @Nullable public com.android.permission.persistence.RuntimePermissionsState readForUser(@NonNull android.os.UserHandle); - method public void writeForUser(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle); - } - - public final class RuntimePermissionsState { - ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>); - method @Nullable public String getFingerprint(); - method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getPackagePermissions(); - method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getSharedUserPermissions(); - method public int getVersion(); - field public static final int NO_VERSION = -1; // 0xffffffff - } - - public static final class RuntimePermissionsState.PermissionState { - ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int); - method public int getFlags(); - method @NonNull public String getName(); - method public boolean isGranted(); - } - -} - -package com.android.role.persistence { - - public interface RolesPersistence { - method @NonNull public static com.android.role.persistence.RolesPersistence createInstance(); - method public void deleteForUser(@NonNull android.os.UserHandle); - method @Nullable public com.android.role.persistence.RolesState readForUser(@NonNull android.os.UserHandle); - method public void writeForUser(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle); - } - - public final class RolesState { - ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>); - method @Nullable public String getPackagesHash(); - method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles(); - method public int getVersion(); - } - -} - diff --git a/apex/permission/service/api/system-server-current.txt b/apex/permission/service/api/system-server-current.txt new file mode 100644 index 000000000000..c76cc3275737 --- /dev/null +++ b/apex/permission/service/api/system-server-current.txt @@ -0,0 +1,46 @@ +// Signature format: 2.0 +package com.android.permission.persistence { + + public interface RuntimePermissionsPersistence { + method @NonNull public static com.android.permission.persistence.RuntimePermissionsPersistence createInstance(); + method public void deleteForUser(@NonNull android.os.UserHandle); + method @Nullable public com.android.permission.persistence.RuntimePermissionsState readForUser(@NonNull android.os.UserHandle); + method public void writeForUser(@NonNull com.android.permission.persistence.RuntimePermissionsState, @NonNull android.os.UserHandle); + } + + public final class RuntimePermissionsState { + ctor public RuntimePermissionsState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>, @NonNull java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>>); + method @Nullable public String getFingerprint(); + method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getPackagePermissions(); + method @NonNull public java.util.Map<java.lang.String,java.util.List<com.android.permission.persistence.RuntimePermissionsState.PermissionState>> getSharedUserPermissions(); + method public int getVersion(); + field public static final int NO_VERSION = -1; // 0xffffffff + } + + public static final class RuntimePermissionsState.PermissionState { + ctor public RuntimePermissionsState.PermissionState(@NonNull String, boolean, int); + method public int getFlags(); + method @NonNull public String getName(); + method public boolean isGranted(); + } + +} + +package com.android.role.persistence { + + public interface RolesPersistence { + method @NonNull public static com.android.role.persistence.RolesPersistence createInstance(); + method public void deleteForUser(@NonNull android.os.UserHandle); + method @Nullable public com.android.role.persistence.RolesState readForUser(@NonNull android.os.UserHandle); + method public void writeForUser(@NonNull com.android.role.persistence.RolesState, @NonNull android.os.UserHandle); + } + + public final class RolesState { + ctor public RolesState(int, @Nullable String, @NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>>); + method @Nullable public String getPackagesHash(); + method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRoles(); + method public int getVersion(); + } + +} + diff --git a/apex/permission/service/api/system-server-removed.txt b/apex/permission/service/api/system-server-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/apex/permission/service/api/system-server-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/apex/permission/tests/Android.bp b/apex/permission/tests/Android.bp index a1f7a544434c..271e328c1139 100644 --- a/apex/permission/tests/Android.bp +++ b/apex/permission/tests/Android.bp @@ -19,7 +19,7 @@ android_test { "java/**/*.kt", ], static_libs: [ - "service-permission", + "service-permission.impl", "androidx.test.rules", "androidx.test.ext.junit", "androidx.test.ext.truth", diff --git a/cmds/idmap2/idmap2/CommandUtils.cpp b/cmds/idmap2/idmap2/CommandUtils.cpp index e058cd6e7e70..8f5845bf2e53 100644 --- a/cmds/idmap2/idmap2/CommandUtils.cpp +++ b/cmds/idmap2/idmap2/CommandUtils.cpp @@ -29,7 +29,7 @@ using android::idmap2::Result; using android::idmap2::Unit; Result<Unit> Verify(const std::string& idmap_path, const std::string& target_path, - const std::string& overlay_path, uint32_t fulfilled_policies, + const std::string& overlay_path, PolicyBitmask fulfilled_policies, bool enforce_overlayable) { SYSTRACE << "Verify " << idmap_path; std::ifstream fin(idmap_path); diff --git a/cmds/idmap2/idmap2/CommandUtils.h b/cmds/idmap2/idmap2/CommandUtils.h index 99605de30988..e717e046d15d 100644 --- a/cmds/idmap2/idmap2/CommandUtils.h +++ b/cmds/idmap2/idmap2/CommandUtils.h @@ -17,12 +17,13 @@ #ifndef IDMAP2_IDMAP2_COMMAND_UTILS_H_ #define IDMAP2_IDMAP2_COMMAND_UTILS_H_ +#include "idmap2/PolicyUtils.h" #include "idmap2/Result.h" android::idmap2::Result<android::idmap2::Unit> Verify(const std::string& idmap_path, const std::string& target_path, const std::string& overlay_path, - uint32_t fulfilled_policies, + PolicyBitmask fulfilled_policies, bool enforce_overlayable); #endif // IDMAP2_IDMAP2_COMMAND_UTILS_H_ diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp index 908d96612269..f95b73f17222 100644 --- a/cmds/idmap2/idmap2d/Idmap2Service.cpp +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -70,12 +70,12 @@ PolicyBitmask ConvertAidlArgToPolicyBitmask(int32_t arg) { } Status GetCrc(const std::string& apk_path, uint32_t* out_crc) { - const auto overlay_zip = ZipFile::Open(apk_path); - if (!overlay_zip) { + const auto zip = ZipFile::Open(apk_path); + if (!zip) { return error(StringPrintf("failed to open apk %s", apk_path.c_str())); } - const auto crc = GetPackageCrc(*overlay_zip); + const auto crc = GetPackageCrc(*zip); if (!crc) { return error(crc.GetErrorMessage()); } @@ -121,6 +121,7 @@ Status Idmap2Service::verifyIdmap(const std::string& target_apk_path, bool* _aidl_return) { SYSTRACE << "Idmap2Service::verifyIdmap " << overlay_apk_path; assert(_aidl_return); + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); std::ifstream fin(idmap_path); const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin); @@ -156,13 +157,10 @@ Status Idmap2Service::verifyIdmap(const std::string& target_apk_path, auto up_to_date = header->IsUpToDate(target_apk_path.c_str(), overlay_apk_path.c_str(), target_crc, overlay_crc, - fulfilled_policies, enforce_overlayable); - if (!up_to_date) { - *_aidl_return = false; - return error(up_to_date.GetErrorMessage()); - } + ConvertAidlArgToPolicyBitmask(fulfilled_policies), enforce_overlayable); - return ok(); + *_aidl_return = static_cast<bool>(up_to_date); + return *_aidl_return ? ok() : error(up_to_date.GetErrorMessage()); } Status Idmap2Service::createIdmap(const std::string& target_apk_path, diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h index 8f25b8d6a734..0f05592b70f3 100644 --- a/cmds/idmap2/include/idmap2/Idmap.h +++ b/cmds/idmap2/include/idmap2/Idmap.h @@ -141,9 +141,9 @@ class IdmapHeader { // field *must* be incremented. Because of this, we know that if the idmap // header is up-to-date the entire file is up-to-date. Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, - uint32_t fulfilled_policies, bool enforce_overlayable) const; + PolicyBitmask fulfilled_policies, bool enforce_overlayable) const; Result<Unit> IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc, - uint32_t overlay_crc, uint32_t fulfilled_policies, + uint32_t overlay_crc, PolicyBitmask fulfilled_policies, bool enforce_overlayable) const; void accept(Visitor* v) const; diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 0bea21735b24..23c25a7089de 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -115,8 +115,7 @@ std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& s uint8_t enforce_overlayable; if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) || !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) || - !Read32(stream, &idmap_header->fulfilled_policies_) || - !Read8(stream, &enforce_overlayable) || + !Read32(stream, &idmap_header->fulfilled_policies_) || !Read8(stream, &enforce_overlayable) || !ReadString256(stream, idmap_header->target_path_) || !ReadString256(stream, idmap_header->overlay_path_)) { return nullptr; @@ -134,7 +133,8 @@ std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& s } Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path, - uint32_t fulfilled_policies, bool enforce_overlayable) const { + PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const { const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path); if (!target_zip) { return Error("failed to open target %s", target_path); @@ -161,7 +161,8 @@ Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overla Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overlay_path, uint32_t target_crc, uint32_t overlay_crc, - uint32_t fulfilled_policies, bool enforce_overlayable) const { + PolicyBitmask fulfilled_policies, + bool enforce_overlayable) const { if (magic_ != kIdmapMagic) { return Error("bad magic: actual 0x%08x, expected 0x%08x", magic_, kIdmapMagic); } @@ -187,8 +188,7 @@ Result<Unit> IdmapHeader::IsUpToDate(const char* target_path, const char* overla if (enforce_overlayable != enforce_overlayable_) { return Error("bad enforce overlayable: idmap version %s, file system version %s", - enforce_overlayable ? "true" : "false", - enforce_overlayable_ ? "true" : "false"); + enforce_overlayable ? "true" : "false", enforce_overlayable_ ? "true" : "false"); } if (strcmp(target_path, target_path_) != 0) { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 052358bb8397..e2fd511a222b 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -566,6 +566,7 @@ message Atom { DataUsageBytesTransfer data_usage_bytes_transfer = 10082 [(module) = "framework"]; BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered = 10083 [(module) = "framework"]; + DNDModeProto dnd_mode_rule = 10084 [(module) = "framework"]; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -6083,6 +6084,76 @@ message PackageNotificationChannelPreferences { } /** + * Atom that represents an item in the list of Do Not Disturb rules, pulled from + * NotificationManagerService.java. + */ +message DNDModeProto { + enum Mode { + ROOT_CONFIG = -1; // Used to distinguish the config (one per user) from the rules. + ZEN_MODE_OFF = 0; + ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; + ZEN_MODE_NO_INTERRUPTIONS = 2; + ZEN_MODE_ALARMS = 3; + } + optional int32 user = 1; // Android user ID (0, 1, 10, ...) + optional bool enabled = 2; // true for ROOT_CONFIG if a manualRule is enabled + optional bool channels_bypassing = 3; // only valid for ROOT_CONFIG + optional Mode zen_mode = 4; + // id is one of the system default rule IDs, or empty + // May also be "MANUAL_RULE" to indicate app-activation of the manual rule. + optional string id = 5; + optional int32 uid = 6 [(is_uid) = true]; // currently only SYSTEM_UID or 0 for other + optional DNDPolicyProto policy = 7; +} + +/** + * Atom that represents a Do Not Disturb policy, an optional detail proto for DNDModeProto. + */ +message DNDPolicyProto { + enum State { + STATE_UNSET = 0; + STATE_ALLOW = 1; + STATE_DISALLOW = 2; + } + optional State calls = 1; + optional State repeat_callers = 2; + optional State messages = 3; + optional State conversations = 4; + optional State reminders = 5; + optional State events = 6; + optional State alarms = 7; + optional State media = 8; + optional State system = 9; + optional State fullscreen = 10; + optional State lights = 11; + optional State peek = 12; + optional State status_bar = 13; + optional State badge = 14; + optional State ambient = 15; + optional State notification_list = 16; + + enum PeopleType { + PEOPLE_UNSET = 0; + PEOPLE_ANYONE = 1; + PEOPLE_CONTACTS = 2; + PEOPLE_STARRED = 3; + PEOPLE_NONE = 4; + } + + optional PeopleType allow_calls_from = 17; + optional PeopleType allow_messages_from = 18; + + enum ConversationType { + CONV_UNSET = 0; + CONV_ANYONE = 1; + CONV_IMPORTANT = 2; + CONV_NONE = 3; + } + + optional ConversationType allow_conversations_from = 19; +} + +/** * Atom that contains a list of a package's channel group preferences, pulled from * NotificationManagerService.java. */ @@ -6333,6 +6404,16 @@ message ContentCaptureServiceEvents { SET_WHITELIST = 3; SET_DISABLED = 4; ON_USER_DATA_REMOVED = 5; + ON_DATA_SHARE_REQUEST = 6; + ACCEPT_DATA_SHARE_REQUEST = 7; + REJECT_DATA_SHARE_REQUEST = 8; + DATA_SHARE_WRITE_FINISHED = 9; + DATA_SHARE_ERROR_IOEXCEPTION = 10; + DATA_SHARE_ERROR_EMPTY_DATA = 11; + DATA_SHARE_ERROR_CLIENT_PIPE_FAIL = 12; + DATA_SHARE_ERROR_SERVICE_PIPE_FAIL = 13; + DATA_SHARE_ERROR_CONCURRENT_REQUEST = 14; + DATA_SHARE_ERROR_TIMEOUT_INTERRUPTED = 15; } optional Event event = 1; // component/package of content capture service. diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 97b704ccc1c9..b608a343fc7d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -601,20 +601,6 @@ public class ActivityManager { @TestApi public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2; - // TODO: remove this when development is done. - // These are debug flags used between OomAdjuster and AppOpsService to detect and report absence - // of the real flags. - /** @hide */ - public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q = 1 << 27; - /** @hide */ - public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q = 1 << 28; - /** @hide */ - public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 29; - /** @hide */ - public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA = 1 << 30; - /** @hide */ - public static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31; - /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/ @TestApi public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION @@ -653,29 +639,9 @@ public class ActivityManager { */ public static void printCapabilitiesFull(PrintWriter pw, @ProcessCapability int caps) { printCapabilitiesSummary(pw, caps); - if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) { - pw.print(" !L"); - } - if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { - pw.print(" !C"); - } - if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q) != 0) { - pw.print(" !Cq"); - } - if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { - pw.print(" !M"); - } - if ((caps & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q) != 0) { - pw.print(" !Mq"); - } final int remain = caps & ~(PROCESS_CAPABILITY_FOREGROUND_LOCATION | PROCESS_CAPABILITY_FOREGROUND_CAMERA - | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE - | DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION - | DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA - | DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q - | DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE - | DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q); + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE); if (remain != 0) { pw.print('+'); pw.print(remain); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 2ce55219506f..a5965bc7f85f 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -398,13 +398,6 @@ public abstract class ActivityManagerInternal { */ public abstract boolean isUidCurrentlyInstrumented(int uid); - /** - * Show a debug toast, asking user to file a bugreport. - */ - // TODO: remove this toast after feature development is done - public abstract void showWhileInUseDebugToast(int uid, int op, int mode); - - /** Is this a device owner app? */ public abstract boolean isDeviceOwner(int uid); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 108b9eec34fb..812ca4aefb9b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -123,6 +123,7 @@ import android.os.SystemProperties; import android.os.TelephonyServiceManager; import android.os.Trace; import android.os.UserHandle; +import android.os.UserManager; import android.permission.IPermissionManager; import android.provider.BlockedNumberContract; import android.provider.CalendarContract; @@ -6816,7 +6817,11 @@ public final class ActivityThread extends ClientTransactionHandler { throw ex.rethrowFromSystemServer(); } if (holder == null) { - Slog.e(TAG, "Failed to find provider info for " + auth); + if (UserManager.get(c).isUserUnlocked(userId)) { + Slog.e(TAG, "Failed to find provider info for " + auth); + } else { + Slog.w(TAG, "Failed to find provider info for " + auth + " (user not unlocked)"); + } return null; } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 23c86029f3be..6d49add65c5b 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -1072,7 +1072,7 @@ public class CameraDeviceImpl extends CameraDevice * @param lastFrameNumber last frame number returned from binder. * @param repeatingRequestTypes the repeating requests' types. */ - private void checkEarlyTriggerSequenceCompleteLocked( + private void checkEarlyTriggerSequenceComplete( final int requestId, final long lastFrameNumber, final int[] repeatingRequestTypes) { // lastFrameNumber being equal to NO_FRAMES_CAPTURED means that the request @@ -1212,7 +1212,7 @@ public class CameraDeviceImpl extends CameraDevice if (repeating) { if (mRepeatingRequestId != REQUEST_ID_NONE) { - checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId, + checkEarlyTriggerSequenceComplete(mRepeatingRequestId, requestInfo.getLastFrameNumber(), mRepeatingRequestTypes); } @@ -1269,7 +1269,7 @@ public class CameraDeviceImpl extends CameraDevice return; } - checkEarlyTriggerSequenceCompleteLocked(requestId, lastFrameNumber, requestTypes); + checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber, requestTypes); } } } @@ -1302,7 +1302,7 @@ public class CameraDeviceImpl extends CameraDevice long lastFrameNumber = mRemoteDevice.flush(); if (mRepeatingRequestId != REQUEST_ID_NONE) { - checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId, lastFrameNumber, + checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber, mRepeatingRequestTypes); mRepeatingRequestId = REQUEST_ID_NONE; mRepeatingRequestTypes = null; @@ -1442,135 +1442,78 @@ public class CameraDeviceImpl extends CameraDevice long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber(); long completedZslStillFrameNumber = mFrameNumberTracker.getCompletedZslStillFrameNumber(); - + boolean isReprocess = false; Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator(); while (iter.hasNext()) { final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); + boolean sequenceCompleted = false; final int requestId = requestLastFrameNumbers.getRequestId(); final CaptureCallbackHolder holder; - if (mRemoteDevice == null) { - Log.w(TAG, "Camera closed while checking sequences"); - return; - } - if (!requestLastFrameNumbers.isSequenceCompleted()) { - long lastRegularFrameNumber = - requestLastFrameNumbers.getLastRegularFrameNumber(); - long lastReprocessFrameNumber = - requestLastFrameNumbers.getLastReprocessFrameNumber(); - long lastZslStillFrameNumber = - requestLastFrameNumbers.getLastZslStillFrameNumber(); - if (lastRegularFrameNumber <= completedFrameNumber - && lastReprocessFrameNumber <= completedReprocessFrameNumber - && lastZslStillFrameNumber <= completedZslStillFrameNumber) { - Log.v(TAG, String.format( - "Mark requestId %d as completed, because lastRegularFrame %d " - + "is <= %d, lastReprocessFrame %d is <= %d, " - + "lastZslStillFrame %d is <= %d", requestId, - lastRegularFrameNumber, completedFrameNumber, - lastReprocessFrameNumber, completedReprocessFrameNumber, - lastZslStillFrameNumber, completedZslStillFrameNumber)); - requestLastFrameNumbers.markSequenceCompleted(); + synchronized(mInterfaceLock) { + if (mRemoteDevice == null) { + Log.w(TAG, "Camera closed while checking sequences"); + return; } - // Call onCaptureSequenceCompleted int index = mCaptureCallbackMap.indexOfKey(requestId); holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null; - if (holder != null && requestLastFrameNumbers.isSequenceCompleted()) { - Runnable resultDispatch = new Runnable() { - @Override - public void run() { - if (!CameraDeviceImpl.this.isClosed()){ - if (DEBUG) { - Log.d(TAG, String.format( - "fire sequence complete for request %d", - requestId)); - } - - holder.getCallback().onCaptureSequenceCompleted( - CameraDeviceImpl.this, - requestId, - requestLastFrameNumbers.getLastFrameNumber()); - } + if (holder != null) { + long lastRegularFrameNumber = + requestLastFrameNumbers.getLastRegularFrameNumber(); + long lastReprocessFrameNumber = + requestLastFrameNumbers.getLastReprocessFrameNumber(); + long lastZslStillFrameNumber = + requestLastFrameNumbers.getLastZslStillFrameNumber(); + // check if it's okay to remove request from mCaptureCallbackMap + if (lastRegularFrameNumber <= completedFrameNumber + && lastReprocessFrameNumber <= completedReprocessFrameNumber + && lastZslStillFrameNumber <= completedZslStillFrameNumber) { + sequenceCompleted = true; + mCaptureCallbackMap.removeAt(index); + if (DEBUG) { + Log.v(TAG, String.format( + "Remove holder for requestId %d, because lastRegularFrame %d " + + "is <= %d, lastReprocessFrame %d is <= %d, " + + "lastZslStillFrame %d is <= %d", requestId, + lastRegularFrameNumber, completedFrameNumber, + lastReprocessFrameNumber, completedReprocessFrameNumber, + lastZslStillFrameNumber, completedZslStillFrameNumber)); } - }; - final long ident = Binder.clearCallingIdentity(); - try { - holder.getExecutor().execute(resultDispatch); - } finally { - Binder.restoreCallingIdentity(ident); } } } - if (requestLastFrameNumbers.isSequenceCompleted() && - requestLastFrameNumbers.isInflightCompleted()) { - int index = mCaptureCallbackMap.indexOfKey(requestId); - if (index >= 0) { - mCaptureCallbackMap.removeAt(index); - } - if (DEBUG) { - Log.v(TAG, String.format( - "Remove holder for requestId %d", requestId)); - } + // If no callback is registered for this requestId or sequence completed, remove it + // from the frame number->request pair because it's not needed anymore. + if (holder == null || sequenceCompleted) { iter.remove(); } - } - } - - private void removeCompletedCallbackHolderLocked(long lastCompletedRegularFrameNumber, - long lastCompletedReprocessFrameNumber, long lastCompletedZslStillFrameNumber) { - if (DEBUG) { - Log.v(TAG, String.format("remove completed callback holders for " - + "lastCompletedRegularFrameNumber %d, " - + "lastCompletedReprocessFrameNumber %d, " - + "lastCompletedZslStillFrameNumber %d", - lastCompletedRegularFrameNumber, - lastCompletedReprocessFrameNumber, - lastCompletedZslStillFrameNumber)); - } - - Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator(); - while (iter.hasNext()) { - final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); - final int requestId = requestLastFrameNumbers.getRequestId(); - final CaptureCallbackHolder holder; - if (mRemoteDevice == null) { - Log.w(TAG, "Camera closed while removing completed callback holders"); - return; - } - long lastRegularFrameNumber = - requestLastFrameNumbers.getLastRegularFrameNumber(); - long lastReprocessFrameNumber = - requestLastFrameNumbers.getLastReprocessFrameNumber(); - long lastZslStillFrameNumber = - requestLastFrameNumbers.getLastZslStillFrameNumber(); - - if (lastRegularFrameNumber <= lastCompletedRegularFrameNumber - && lastReprocessFrameNumber <= lastCompletedReprocessFrameNumber - && lastZslStillFrameNumber <= lastCompletedZslStillFrameNumber) { + // Call onCaptureSequenceCompleted + if (sequenceCompleted) { + Runnable resultDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDeviceImpl.this.isClosed()){ + if (DEBUG) { + Log.d(TAG, String.format( + "fire sequence complete for request %d", + requestId)); + } - if (requestLastFrameNumbers.isSequenceCompleted()) { - int index = mCaptureCallbackMap.indexOfKey(requestId); - if (index >= 0) { - mCaptureCallbackMap.removeAt(index); - } - if (DEBUG) { - Log.v(TAG, String.format( - "Remove holder for requestId %d, because lastRegularFrame %d " - + "is <= %d, lastReprocessFrame %d is <= %d, " - + "lastZslStillFrame %d is <= %d", requestId, - lastRegularFrameNumber, lastCompletedRegularFrameNumber, - lastReprocessFrameNumber, lastCompletedReprocessFrameNumber, - lastZslStillFrameNumber, lastCompletedZslStillFrameNumber)); - } - iter.remove(); - } else { - if (DEBUG) { - Log.v(TAG, "Sequence not yet completed for request id " + requestId); + holder.getCallback().onCaptureSequenceCompleted( + CameraDeviceImpl.this, + requestId, + requestLastFrameNumbers.getLastFrameNumber()); + } } - requestLastFrameNumbers.markInflightCompleted(); + }; + final long ident = Binder.clearCallingIdentity(); + try { + holder.getExecutor().execute(resultDispatch); + } finally { + Binder.restoreCallingIdentity(ident); } } } @@ -1759,12 +1702,6 @@ public class CameraDeviceImpl extends CameraDevice return; } - // Remove all capture callbacks now that device has gone to IDLE state. - removeCompletedCallbackHolderLocked( - Long.MAX_VALUE, /*lastCompletedRegularFrameNumber*/ - Long.MAX_VALUE, /*lastCompletedReprocessFrameNumber*/ - Long.MAX_VALUE /*lastCompletedZslStillFrameNumber*/); - if (!CameraDeviceImpl.this.mIdle) { final long ident = Binder.clearCallingIdentity(); try { @@ -1810,7 +1747,7 @@ public class CameraDeviceImpl extends CameraDevice return; } - checkEarlyTriggerSequenceCompleteLocked(mRepeatingRequestId, lastFrameNumber, + checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber, mRepeatingRequestTypes); // Check if there is already a new repeating request if (mRepeatingRequestId == repeatingRequestId) { @@ -1829,18 +1766,9 @@ public class CameraDeviceImpl extends CameraDevice public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) { int requestId = resultExtras.getRequestId(); final long frameNumber = resultExtras.getFrameNumber(); - final long lastCompletedRegularFrameNumber = - resultExtras.getLastCompletedRegularFrameNumber(); - final long lastCompletedReprocessFrameNumber = - resultExtras.getLastCompletedReprocessFrameNumber(); - final long lastCompletedZslFrameNumber = - resultExtras.getLastCompletedZslFrameNumber(); if (DEBUG) { - Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber - + ": completedRegularFrameNumber " + lastCompletedRegularFrameNumber - + ", completedReprocessFrameNUmber " + lastCompletedReprocessFrameNumber - + ", completedZslFrameNumber " + lastCompletedZslFrameNumber); + Log.d(TAG, "Capture started for id " + requestId + " frame number " + frameNumber); } final CaptureCallbackHolder holder; @@ -1856,12 +1784,6 @@ public class CameraDeviceImpl extends CameraDevice return; } - // Check if it's okay to remove completed callbacks from mCaptureCallbackMap. - // A callback is completed if the corresponding inflight request has been removed - // from the inflight queue in cameraservice. - removeCompletedCallbackHolderLocked(lastCompletedRegularFrameNumber, - lastCompletedReprocessFrameNumber, lastCompletedZslFrameNumber); - // Get the callback for this frame ID, if there is one holder = CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId); diff --git a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java index 413caf5e22e0..1d9d644c9306 100644 --- a/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraOfflineSessionImpl.java @@ -182,12 +182,6 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession return; } - // Remove all capture callbacks now that device has gone to IDLE state. - removeCompletedCallbackHolderLocked( - Long.MAX_VALUE, /*lastCompletedRegularFrameNumber*/ - Long.MAX_VALUE, /*lastCompletedReprocessFrameNumber*/ - Long.MAX_VALUE /*lastCompletedZslStillFrameNumber*/); - Runnable idleDispatch = new Runnable() { @Override public void run() { @@ -210,22 +204,10 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession public void onCaptureStarted(final CaptureResultExtras resultExtras, final long timestamp) { int requestId = resultExtras.getRequestId(); final long frameNumber = resultExtras.getFrameNumber(); - final long lastCompletedRegularFrameNumber = - resultExtras.getLastCompletedRegularFrameNumber(); - final long lastCompletedReprocessFrameNumber = - resultExtras.getLastCompletedReprocessFrameNumber(); - final long lastCompletedZslFrameNumber = - resultExtras.getLastCompletedZslFrameNumber(); final CaptureCallbackHolder holder; synchronized(mInterfaceLock) { - // Check if it's okay to remove completed callbacks from mCaptureCallbackMap. - // A callback is completed if the corresponding inflight request has been removed - // from the inflight queue in cameraservice. - removeCompletedCallbackHolderLocked(lastCompletedRegularFrameNumber, - lastCompletedReprocessFrameNumber, lastCompletedZslFrameNumber); - // Get the callback for this frame ID, if there is one holder = CameraOfflineSessionImpl.this.mCaptureCallbackMap.get(requestId); @@ -619,61 +601,6 @@ public class CameraOfflineSessionImpl extends CameraOfflineSession } } - private void removeCompletedCallbackHolderLocked(long lastCompletedRegularFrameNumber, - long lastCompletedReprocessFrameNumber, long lastCompletedZslStillFrameNumber) { - if (DEBUG) { - Log.v(TAG, String.format("remove completed callback holders for " - + "lastCompletedRegularFrameNumber %d, " - + "lastCompletedReprocessFrameNumber %d, " - + "lastCompletedZslStillFrameNumber %d", - lastCompletedRegularFrameNumber, - lastCompletedReprocessFrameNumber, - lastCompletedZslStillFrameNumber)); - } - - boolean isReprocess = false; - Iterator<RequestLastFrameNumbersHolder> iter = - mOfflineRequestLastFrameNumbersList.iterator(); - while (iter.hasNext()) { - final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); - final int requestId = requestLastFrameNumbers.getRequestId(); - final CaptureCallbackHolder holder; - - int index = mCaptureCallbackMap.indexOfKey(requestId); - holder = (index >= 0) ? - mCaptureCallbackMap.valueAt(index) : null; - if (holder != null) { - long lastRegularFrameNumber = - requestLastFrameNumbers.getLastRegularFrameNumber(); - long lastReprocessFrameNumber = - requestLastFrameNumbers.getLastReprocessFrameNumber(); - long lastZslStillFrameNumber = - requestLastFrameNumbers.getLastZslStillFrameNumber(); - if (lastRegularFrameNumber <= lastCompletedRegularFrameNumber - && lastReprocessFrameNumber <= lastCompletedReprocessFrameNumber - && lastZslStillFrameNumber <= lastCompletedZslStillFrameNumber) { - if (requestLastFrameNumbers.isSequenceCompleted()) { - mCaptureCallbackMap.removeAt(index); - if (DEBUG) { - Log.v(TAG, String.format( - "Remove holder for requestId %d, because lastRegularFrame %d " - + "is <= %d, lastReprocessFrame %d is <= %d, " - + "lastZslStillFrame %d is <= %d", requestId, - lastRegularFrameNumber, lastCompletedRegularFrameNumber, - lastReprocessFrameNumber, lastCompletedReprocessFrameNumber, - lastZslStillFrameNumber, lastCompletedZslStillFrameNumber)); - } - - iter.remove(); - } else { - Log.e(TAG, "Sequence not yet completed for request id " + requestId); - continue; - } - } - } - } - } - public void notifyFailedSwitch() { synchronized(mInterfaceLock) { Runnable switchFailDispatch = new Runnable() { diff --git a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java index 5d9da73fd5c0..1ff5bd562f2e 100644 --- a/core/java/android/hardware/camera2/impl/CaptureResultExtras.java +++ b/core/java/android/hardware/camera2/impl/CaptureResultExtras.java @@ -30,9 +30,6 @@ public class CaptureResultExtras implements Parcelable { private int partialResultCount; private int errorStreamId; private String errorPhysicalCameraId; - private long lastCompletedRegularFrameNumber; - private long lastCompletedReprocessFrameNumber; - private long lastCompletedZslFrameNumber; public static final @android.annotation.NonNull Parcelable.Creator<CaptureResultExtras> CREATOR = new Parcelable.Creator<CaptureResultExtras>() { @@ -54,9 +51,7 @@ public class CaptureResultExtras implements Parcelable { public CaptureResultExtras(int requestId, int subsequenceId, int afTriggerId, int precaptureTriggerId, long frameNumber, int partialResultCount, int errorStreamId, - String errorPhysicalCameraId, long lastCompletedRegularFrameNumber, - long lastCompletedReprocessFrameNumber, - long lastCompletedZslFrameNumber) { + String errorPhysicalCameraId) { this.requestId = requestId; this.subsequenceId = subsequenceId; this.afTriggerId = afTriggerId; @@ -65,9 +60,6 @@ public class CaptureResultExtras implements Parcelable { this.partialResultCount = partialResultCount; this.errorStreamId = errorStreamId; this.errorPhysicalCameraId = errorPhysicalCameraId; - this.lastCompletedRegularFrameNumber = lastCompletedRegularFrameNumber; - this.lastCompletedReprocessFrameNumber = lastCompletedReprocessFrameNumber; - this.lastCompletedZslFrameNumber = lastCompletedZslFrameNumber; } @Override @@ -90,9 +82,6 @@ public class CaptureResultExtras implements Parcelable { } else { dest.writeBoolean(false); } - dest.writeLong(lastCompletedRegularFrameNumber); - dest.writeLong(lastCompletedReprocessFrameNumber); - dest.writeLong(lastCompletedZslFrameNumber); } public void readFromParcel(Parcel in) { @@ -107,9 +96,6 @@ public class CaptureResultExtras implements Parcelable { if (errorPhysicalCameraIdPresent) { errorPhysicalCameraId = in.readString(); } - lastCompletedRegularFrameNumber = in.readLong(); - lastCompletedReprocessFrameNumber = in.readLong(); - lastCompletedZslFrameNumber = in.readLong(); } public String getErrorPhysicalCameraId() { @@ -143,16 +129,4 @@ public class CaptureResultExtras implements Parcelable { public int getErrorStreamId() { return errorStreamId; } - - public long getLastCompletedRegularFrameNumber() { - return lastCompletedRegularFrameNumber; - } - - public long getLastCompletedReprocessFrameNumber() { - return lastCompletedReprocessFrameNumber; - } - - public long getLastCompletedZslFrameNumber() { - return lastCompletedZslFrameNumber; - } } diff --git a/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java b/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java index 0ee4ebc1aa87..bd1df9e1ac7d 100644 --- a/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java +++ b/core/java/android/hardware/camera2/impl/RequestLastFrameNumbersHolder.java @@ -38,10 +38,6 @@ public class RequestLastFrameNumbersHolder { // The last ZSL still capture frame number for this request ID. It's // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no zsl request. private final long mLastZslStillFrameNumber; - // Whether the sequence is completed. (only consider capture result) - private boolean mSequenceCompleted; - // Whether the inflight request is completed. (consider result, buffers, and notifies) - private boolean mInflightCompleted; /** * Create a request-last-frame-numbers holder with a list of requests, request ID, and @@ -93,8 +89,6 @@ public class RequestLastFrameNumbersHolder { mLastReprocessFrameNumber = lastReprocessFrameNumber; mLastZslStillFrameNumber = lastZslStillFrameNumber; mRequestId = requestInfo.getRequestId(); - mSequenceCompleted = false; - mInflightCompleted = false; } /** @@ -143,8 +137,6 @@ public class RequestLastFrameNumbersHolder { mLastZslStillFrameNumber = lastZslStillFrameNumber; mLastReprocessFrameNumber = CameraCaptureSession.CaptureCallback.NO_FRAMES_CAPTURED; mRequestId = requestId; - mSequenceCompleted = false; - mInflightCompleted = false; } /** @@ -185,34 +177,5 @@ public class RequestLastFrameNumbersHolder { public int getRequestId() { return mRequestId; } - - /** - * Return whether the capture sequence is completed. - */ - public boolean isSequenceCompleted() { - return mSequenceCompleted; - } - - /** - * Mark the capture sequence as completed. - */ - public void markSequenceCompleted() { - mSequenceCompleted = true; - } - - /** - * Return whether the inflight capture is completed. - */ - public boolean isInflightCompleted() { - return mInflightCompleted; - } - - /** - * Mark the inflight capture as completed. - */ - public void markInflightCompleted() { - mInflightCompleted = true; - } - } diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index fdd578c419d8..fbc9ac3229c3 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -109,12 +109,11 @@ public class LegacyCameraDevice implements AutoCloseable { } if (holder == null) { return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, - ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null, - ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE); + ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE, null); } return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(), /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(), - /*partialResultCount*/1, errorStreamId, null, holder.getFrameNumber(), -1, -1); + /*partialResultCount*/1, errorStreamId, null); } /** diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 65e772cb5ebb..482d2d2192b8 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -357,6 +357,7 @@ public abstract class NetworkAgent { final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacyType, config.legacyTypeName, ""); ni.setIsAvailable(true); + ni.setExtraInfo(config.getLegacyExtraInfo()); return ni; } diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java index fee868a93be4..fe1268d79b89 100644 --- a/core/java/android/net/NetworkAgentConfig.java +++ b/core/java/android/net/NetworkAgentConfig.java @@ -185,6 +185,26 @@ public final class NetworkAgentConfig implements Parcelable { return legacyTypeName; } + /** + * The legacy extra info of the agent. The extra info should only be : + * <ul> + * <li>For cellular agents, the APN name.</li> + * <li>For ethernet agents, the interface name.</li> + * </ul> + * @hide + */ + @NonNull + private String mLegacyExtraInfo = ""; + + /** + * The legacy extra info of the agent. + * @hide + */ + @NonNull + public String getLegacyExtraInfo() { + return mLegacyExtraInfo; + } + /** @hide */ public NetworkAgentConfig() { } @@ -201,6 +221,7 @@ public final class NetworkAgentConfig implements Parcelable { skip464xlat = nac.skip464xlat; legacyType = nac.legacyType; legacyTypeName = nac.legacyTypeName; + mLegacyExtraInfo = nac.mLegacyExtraInfo; } } @@ -309,6 +330,18 @@ public final class NetworkAgentConfig implements Parcelable { } /** + * Sets the legacy extra info of the agent. + * @param legacyExtraInfo the legacy extra info. + * @return this builder, to facilitate chaining. + * @hide + */ + @NonNull + public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) { + mConfig.mLegacyExtraInfo = legacyExtraInfo; + return this; + } + + /** * Returns the constructed {@link NetworkAgentConfig} object. */ @NonNull @@ -330,14 +363,15 @@ public final class NetworkAgentConfig implements Parcelable { && skip464xlat == that.skip464xlat && legacyType == that.legacyType && Objects.equals(subscriberId, that.subscriberId) - && Objects.equals(legacyTypeName, that.legacyTypeName); + && Objects.equals(legacyTypeName, that.legacyTypeName) + && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo); } @Override public int hashCode() { return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated, acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId, - skip464xlat, legacyType, legacyTypeName); + skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo); } @Override @@ -353,6 +387,7 @@ public final class NetworkAgentConfig implements Parcelable { + ", legacyType = " + legacyType + ", hasShownBroken = " + hasShownBroken + ", legacyTypeName = '" + legacyTypeName + '\'' + + ", legacyExtraInfo = '" + mLegacyExtraInfo + '\'' + "}"; } @@ -372,6 +407,7 @@ public final class NetworkAgentConfig implements Parcelable { out.writeInt(skip464xlat ? 1 : 0); out.writeInt(legacyType); out.writeString(legacyTypeName); + out.writeString(mLegacyExtraInfo); } public static final @NonNull Creator<NetworkAgentConfig> CREATOR = @@ -388,6 +424,7 @@ public final class NetworkAgentConfig implements Parcelable { networkAgentConfig.skip464xlat = in.readInt() != 0; networkAgentConfig.legacyType = in.readInt(); networkAgentConfig.legacyTypeName = in.readString(); + networkAgentConfig.mLegacyExtraInfo = in.readString(); return networkAgentConfig; } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 1f6555c85a66..0827fef60252 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -78,6 +78,7 @@ public class ZenModeConfig implements Parcelable { private static final int DEFAULT_SOURCE = SOURCE_CONTACT; private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR; + public static final String MANUAL_RULE_ID = "MANUAL_RULE"; public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE"; public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE"; public static final List<String> DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID, @@ -959,6 +960,48 @@ public class ZenModeConfig implements Parcelable { }; /** + * Converts a ZenModeConfig to a ZenPolicy + */ + public ZenPolicy toZenPolicy() { + ZenPolicy.Builder builder = new ZenPolicy.Builder() + .allowCalls(allowCalls + ? ZenModeConfig.getZenPolicySenders(allowCallsFrom) + : ZenPolicy.PEOPLE_TYPE_NONE) + .allowRepeatCallers(allowRepeatCallers) + .allowMessages(allowMessages + ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom) + : ZenPolicy.PEOPLE_TYPE_NONE) + .allowReminders(allowReminders) + .allowEvents(allowEvents) + .allowAlarms(allowAlarms) + .allowMedia(allowMedia) + .allowSystem(allowSystem) + .allowConversations(allowConversations + ? ZenModeConfig.getZenPolicySenders(allowConversationsFrom) + : ZenPolicy.PEOPLE_TYPE_NONE); + if (suppressedVisualEffects == 0) { + builder.showAllVisualEffects(); + } else { + // configs don't have an unset state: wither true or false. + builder.showFullScreenIntent( + (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) == 0); + builder.showLights( + (suppressedVisualEffects & SUPPRESSED_EFFECT_LIGHTS) == 0); + builder.showPeeking( + (suppressedVisualEffects & SUPPRESSED_EFFECT_PEEK) == 0); + builder.showStatusBarIcons( + (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_STATUS_BAR) == 0); + builder.showBadges( + (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_BADGE) == 0); + builder.showInAmbientDisplay( + (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_AMBIENT) == 0); + builder.showInNotificationList( + (suppressedVisualEffects & Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) == 0); + } + return builder.build(); + } + + /** * Converts a zenPolicy to a notificationPolicy using this ZenModeConfig's values as its * defaults for all unset values in zenPolicy */ diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index 87295e1c95b9..6d0bcffe148e 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -24,6 +24,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.proto.ProtoOutputStream; +import java.io.ByteArrayOutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -1111,6 +1112,40 @@ public final class ZenPolicy implements Parcelable { } /** + * Converts a policy to a statsd proto. + * @hides + */ + public byte[] toProto() { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ProtoOutputStream proto = new ProtoOutputStream(bytes); + + proto.write(DNDPolicyProto.CALLS, getPriorityCategoryCalls()); + proto.write(DNDPolicyProto.REPEAT_CALLERS, getPriorityCategoryRepeatCallers()); + proto.write(DNDPolicyProto.MESSAGES, getPriorityCategoryMessages()); + proto.write(DNDPolicyProto.CONVERSATIONS, getPriorityCategoryConversations()); + proto.write(DNDPolicyProto.REMINDERS, getPriorityCategoryReminders()); + proto.write(DNDPolicyProto.EVENTS, getPriorityCategoryEvents()); + proto.write(DNDPolicyProto.ALARMS, getPriorityCategoryAlarms()); + proto.write(DNDPolicyProto.MEDIA, getPriorityCategoryMedia()); + proto.write(DNDPolicyProto.SYSTEM, getPriorityCategorySystem()); + + proto.write(DNDPolicyProto.FULLSCREEN, getVisualEffectFullScreenIntent()); + proto.write(DNDPolicyProto.LIGHTS, getVisualEffectLights()); + proto.write(DNDPolicyProto.PEEK, getVisualEffectPeek()); + proto.write(DNDPolicyProto.STATUS_BAR, getVisualEffectStatusBar()); + proto.write(DNDPolicyProto.BADGE, getVisualEffectBadge()); + proto.write(DNDPolicyProto.AMBIENT, getVisualEffectAmbient()); + proto.write(DNDPolicyProto.NOTIFICATION_LIST, getVisualEffectNotificationList()); + + proto.write(DNDPolicyProto.ALLOW_CALLS_FROM, getPriorityCallSenders()); + proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM, getPriorityMessageSenders()); + proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, getPriorityConversationSenders()); + + proto.flush(); + return bytes.toByteArray(); + } + + /** * Makes deep copy of this ZenPolicy. * @hide */ diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java index ffe2dbe4ccc0..2d91e64b2e67 100644 --- a/core/java/com/android/internal/app/SimpleIconFactory.java +++ b/core/java/com/android/internal/app/SimpleIconFactory.java @@ -19,6 +19,7 @@ package com.android.internal.app; import static android.content.Context.ACTIVITY_SERVICE; import static android.graphics.Paint.DITHER_FLAG; import static android.graphics.Paint.FILTER_BITMAP_FLAG; +import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction; import android.annotation.AttrRes; import android.annotation.NonNull; @@ -54,6 +55,7 @@ import com.android.internal.R; import org.xmlpull.v1.XmlPullParser; import java.nio.ByteBuffer; +import java.util.Optional; /** @@ -64,6 +66,7 @@ import java.nio.ByteBuffer; @Deprecated public class SimpleIconFactory { + private static final SynchronizedPool<SimpleIconFactory> sPool = new SynchronizedPool<>(Runtime.getRuntime().availableProcessors()); @@ -251,7 +254,7 @@ public class SimpleIconFactory { } else if (w > h && h > 0) { scale = (float) w / h; } - Bitmap bitmap = createIconBitmap(icon, scale); + Bitmap bitmap = createIconBitmapNoInsetOrMask(icon, scale); bitmap = maskBitmapToCircle(bitmap); icon = new BitmapDrawable(mContext.getResources(), bitmap); @@ -281,15 +284,19 @@ public class SimpleIconFactory { final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(output); - final Paint paint = new Paint(); - paint.setAntiAlias(true); + final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG + | Paint.FILTER_BITMAP_FLAG); + + // Apply an offset to enable shadow to be drawn + final int size = bitmap.getWidth(); + int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), 1); // Draw mask paint.setColor(0xffffffff); canvas.drawARGB(0, 0, 0, 0); canvas.drawCircle(bitmap.getWidth() / 2f, bitmap.getHeight() / 2f, - bitmap.getWidth() / 2f - 1 /* -1 to avoid circles with flat sides */, + bitmap.getWidth() / 2f - offset, paint); // Draw masked bitmap @@ -306,24 +313,61 @@ public class SimpleIconFactory { } private Bitmap createIconBitmap(Drawable icon, float scale) { - return createIconBitmap(icon, scale, mIconBitmapSize); + return createIconBitmap(icon, scale, mIconBitmapSize, true, false); + } + + private Bitmap createIconBitmapNoInsetOrMask(Drawable icon, float scale) { + return createIconBitmap(icon, scale, mIconBitmapSize, false, true); } /** * @param icon drawable that should be flattened to a bitmap * @param scale the scale to apply before drawing {@param icon} on the canvas + * @param insetAdiForShadow when rendering AdaptiveIconDrawables inset to make room for a shadow + * @param ignoreAdiMask when rendering AdaptiveIconDrawables ignore the current system mask */ - private Bitmap createIconBitmap(Drawable icon, float scale, int size) { + private Bitmap createIconBitmap(Drawable icon, float scale, int size, boolean insetAdiForShadow, + boolean ignoreAdiMask) { Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); mCanvas.setBitmap(bitmap); mOldBounds.set(icon.getBounds()); if (icon instanceof AdaptiveIconDrawable) { - int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), - Math.round(size * (1 - scale) / 2)); - icon.setBounds(offset, offset, size - offset, size - offset); - icon.draw(mCanvas); + final AdaptiveIconDrawable adi = (AdaptiveIconDrawable) icon; + + // By default assumes the output bitmap will have a shadow directly applied and makes + // room for it by insetting. If there are intermediate steps before applying the shadow + // insetting is disableable. + int offset = Math.round(size * (1 - scale) / 2); + if (insetAdiForShadow) { + offset = Math.max((int) Math.ceil(BLUR_FACTOR * size), offset); + } + Rect bounds = new Rect(offset, offset, size - offset, size - offset); + + // AdaptiveIconDrawables are by default masked by the user's icon shape selection. + // If further masking is to be done, directly render to avoid the system masking. + if (ignoreAdiMask) { + final int cX = bounds.width() / 2; + final int cY = bounds.height() / 2; + final float portScale = 1f / (1 + 2 * getExtraInsetFraction()); + final int insetWidth = (int) (bounds.width() / (portScale * 2)); + final int insetHeight = (int) (bounds.height() / (portScale * 2)); + + Rect childRect = new Rect(cX - insetWidth, cY - insetHeight, cX + insetWidth, + cY + insetHeight); + Optional.ofNullable(adi.getBackground()).ifPresent(drawable -> { + drawable.setBounds(childRect); + drawable.draw(mCanvas); + }); + Optional.ofNullable(adi.getForeground()).ifPresent(drawable -> { + drawable.setBounds(childRect); + drawable.draw(mCanvas); + }); + } else { + adi.setBounds(bounds); + adi.draw(mCanvas); + } } else { if (icon instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java index 86a9af3db196..fe0e7d012262 100644 --- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java +++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java @@ -40,8 +40,10 @@ import java.util.List; * resolve it to an activity. */ public class DisplayResolveInfo implements TargetInfo { - // Temporary flag for new chooser delegate behavior. - private static final boolean ENABLE_CHOOSER_DELEGATE = true; + // Temporary flag for new chooser delegate behavior. There are occassional token + // permission errors from bouncing through the delegate. Watch out before reenabling: + // b/157272342 is one example but this issue has been reported many times + private static final boolean ENABLE_CHOOSER_DELEGATE = false; private final ResolveInfo mResolveInfo; private CharSequence mDisplayLabel; diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 1f2ae5f96449..7c7d9312e2db 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -21,6 +21,10 @@ import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_ import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_IN; import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,6 +40,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Typeface; +import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Parcelable; @@ -80,7 +85,7 @@ public class ConversationLayout extends FrameLayout private static final float COLOR_SHIFT_AMOUNT = 60; /** - * Pattren for filter some ingonable characters. + * Pattern for filter some ignorable characters. * p{Z} for any kind of whitespace or invisible separator. * p{C} for any kind of punctuation character. */ @@ -93,8 +98,12 @@ public class ConversationLayout extends FrameLayout public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f); public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f); public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); + public static final Interpolator OVERSHOOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f); public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR = new MessagingPropertyAnimator(); + public static final int IMPORTANCE_ANIM_GROW_DURATION = 250; + public static final int IMPORTANCE_ANIM_SHRINK_DURATION = 200; + public static final int IMPORTANCE_ANIM_SHRINK_DELAY = 25; private List<MessagingMessage> mMessages = new ArrayList<>(); private List<MessagingMessage> mHistoricMessages = new ArrayList<>(); private MessagingLinearLayout mMessagingLinearLayout; @@ -331,14 +340,74 @@ public class ConversationLayout extends FrameLayout mNameReplacement = nameReplacement; } - /** - * Sets this conversation as "important", adding some additional UI treatment. - */ + /** Sets this conversation as "important", adding some additional UI treatment. */ @RemotableViewMethod public void setIsImportantConversation(boolean isImportantConversation) { + setIsImportantConversation(isImportantConversation, false); + } + + /** @hide **/ + public void setIsImportantConversation(boolean isImportantConversation, boolean animate) { mImportantConversation = isImportantConversation; - mImportanceRingView.setVisibility(isImportantConversation - && mIcon.getVisibility() != GONE ? VISIBLE : GONE); + mImportanceRingView.setVisibility(isImportantConversation && mIcon.getVisibility() != GONE + ? VISIBLE : GONE); + + if (animate && isImportantConversation) { + GradientDrawable ring = (GradientDrawable) mImportanceRingView.getDrawable(); + ring.mutate(); + GradientDrawable bg = (GradientDrawable) mConversationIconBadgeBg.getDrawable(); + bg.mutate(); + int ringColor = getResources() + .getColor(R.color.conversation_important_highlight); + int standardThickness = getResources() + .getDimensionPixelSize(R.dimen.importance_ring_stroke_width); + int largeThickness = getResources() + .getDimensionPixelSize(R.dimen.importance_ring_anim_max_stroke_width); + int standardSize = getResources().getDimensionPixelSize( + R.dimen.importance_ring_size); + int baseSize = standardSize - standardThickness * 2; + int bgSize = getResources() + .getDimensionPixelSize(R.dimen.conversation_icon_size_badged); + + ValueAnimator.AnimatorUpdateListener animatorUpdateListener = animation -> { + int strokeWidth = Math.round((float) animation.getAnimatedValue()); + ring.setStroke(strokeWidth, ringColor); + int newSize = baseSize + strokeWidth * 2; + ring.setSize(newSize, newSize); + mImportanceRingView.invalidate(); + }; + + ValueAnimator growAnimation = ValueAnimator.ofFloat(0, largeThickness); + growAnimation.setInterpolator(LINEAR_OUT_SLOW_IN); + growAnimation.setDuration(IMPORTANCE_ANIM_GROW_DURATION); + growAnimation.addUpdateListener(animatorUpdateListener); + + ValueAnimator shrinkAnimation = + ValueAnimator.ofFloat(largeThickness, standardThickness); + shrinkAnimation.setDuration(IMPORTANCE_ANIM_SHRINK_DURATION); + shrinkAnimation.setStartDelay(IMPORTANCE_ANIM_SHRINK_DELAY); + shrinkAnimation.setInterpolator(OVERSHOOT); + shrinkAnimation.addUpdateListener(animatorUpdateListener); + shrinkAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + // Shrink the badge bg so that it doesn't peek behind the animation + bg.setSize(baseSize, baseSize); + mConversationIconBadgeBg.invalidate(); + } + + @Override + public void onAnimationEnd(Animator animation) { + // Reset bg back to normal size + bg.setSize(bgSize, bgSize); + mConversationIconBadgeBg.invalidate(); + } + }); + + AnimatorSet anims = new AnimatorSet(); + anims.playSequentially(growAnimation, shrinkAnimation); + anims.start(); + } } public boolean isImportantConversation() { @@ -1176,7 +1245,6 @@ public class ConversationLayout extends FrameLayout } private void updateContentEndPaddings() { - // Let's make sure the conversation header can't run into the expand button when we're // collapsed and update the paddings of the content int headerPaddingEnd; diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto index ecb4193a2c6c..8e4006aa6861 100644 --- a/core/proto/android/service/notification.proto +++ b/core/proto/android/service/notification.proto @@ -274,4 +274,74 @@ message PackageRemoteViewInfoProto { // Next Tag: 2 message NotificationRemoteViewsProto { repeated PackageRemoteViewInfoProto package_remote_view_info = 1; -}
\ No newline at end of file +} + +/** + * Atom that represents an item in the list of Do Not Disturb rules, pulled from + * NotificationManagerService.java. + */ +message DNDModeProto { + enum Mode { + ROOT_CONFIG = -1; // Used to distinguish the config (one per user) from the rules. + ZEN_MODE_OFF = 0; + ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1; + ZEN_MODE_NO_INTERRUPTIONS = 2; + ZEN_MODE_ALARMS = 3; + } + optional int32 user = 1; // Android user ID (0, 1, 10, ...) + optional bool enabled = 2; // true for ROOT_CONFIG if a manualRule is enabled + optional bool channels_bypassing = 3; // only valid for ROOT_CONFIG + optional Mode zen_mode = 4; + // id is one of the system default rule IDs, or empty + // May also be "MANUAL_RULE" to indicate app-activation of the manual rule. + optional string id = 5; + optional int32 uid = 6; // currently only SYSTEM_UID or 0 for other + optional DNDPolicyProto policy = 7; +} + +/** + * Atom that represents a Do Not Disturb policy, an optional detail proto for DNDModeProto. + */ +message DNDPolicyProto { + enum State { + STATE_UNSET = 0; + STATE_ALLOW = 1; + STATE_DISALLOW = 2; + } + optional State calls = 1; + optional State repeat_callers = 2; + optional State messages = 3; + optional State conversations = 4; + optional State reminders = 5; + optional State events = 6; + optional State alarms = 7; + optional State media = 8; + optional State system = 9; + optional State fullscreen = 10; + optional State lights = 11; + optional State peek = 12; + optional State status_bar = 13; + optional State badge = 14; + optional State ambient = 15; + optional State notification_list = 16; + + enum PeopleType { + PEOPLE_UNSET = 0; + PEOPLE_ANYONE = 1; + PEOPLE_CONTACTS = 2; + PEOPLE_STARRED = 3; + PEOPLE_NONE = 4; + } + + optional PeopleType allow_calls_from = 17; + optional PeopleType allow_messages_from = 18; + + enum ConversationType { + CONV_UNSET = 0; + CONV_ANYONE = 1; + CONV_IMPORTANT = 2; + CONV_NONE = 3; + } + + optional ConversationType allow_conversations_from = 19; +} diff --git a/core/res/res/drawable/conversation_badge_background.xml b/core/res/res/drawable/conversation_badge_background.xml index 0dd0dcda40fb..9e6405dc1040 100644 --- a/core/res/res/drawable/conversation_badge_background.xml +++ b/core/res/res/drawable/conversation_badge_background.xml @@ -22,7 +22,7 @@ android:color="#ffffff"/> <size - android:width="26dp" - android:height="26dp"/> + android:width="20dp" + android:height="20dp"/> </shape> diff --git a/core/res/res/drawable/conversation_badge_ring.xml b/core/res/res/drawable/conversation_badge_ring.xml index 11ba8ad69505..eee53d1c21b5 100644 --- a/core/res/res/drawable/conversation_badge_ring.xml +++ b/core/res/res/drawable/conversation_badge_ring.xml @@ -16,17 +16,18 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="oval"> - - <solid - android:color="@color/transparent"/> + android:shape="oval" +> + <solid android:color="@color/transparent" /> <stroke android:color="@color/conversation_important_highlight" - android:width="2dp"/> + android:width="@dimen/importance_ring_stroke_width" + /> <size - android:width="26dp" - android:height="26dp"/> + android:width="@dimen/importance_ring_size" + android:height="@dimen/importance_ring_size" + /> </shape> diff --git a/core/res/res/layout/chooser_grid_preview_text.xml b/core/res/res/layout/chooser_grid_preview_text.xml index 002917463ab3..1d18648b9ef7 100644 --- a/core/res/res/layout/chooser_grid_preview_text.xml +++ b/core/res/res/layout/chooser_grid_preview_text.xml @@ -44,6 +44,8 @@ android:ellipsize="end" android:fontFamily="@android:string/config_headlineFontFamily" android:textColor="?android:attr/textColorPrimary" + android:textAlignment="gravity" + android:textDirection="locale" android:maxLines="2" android:focusable="true"/> @@ -90,6 +92,8 @@ android:layout_gravity="center_vertical" android:ellipsize="end" android:maxLines="2" + android:textAlignment="gravity" + android:textDirection="locale" android:textAppearance="@style/TextAppearance.DeviceDefault.WindowTitle" android:fontFamily="@android:string/config_headlineFontFamily"/> </LinearLayout> diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml index 9a9d8b96c677..d3857941969b 100644 --- a/core/res/res/layout/notification_template_material_conversation.xml +++ b/core/res/res/layout/notification_template_material_conversation.xml @@ -38,6 +38,8 @@ <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" android:layout_gravity="top|center_horizontal" > @@ -63,13 +65,17 @@ android:layout_height="@dimen/conversation_icon_size_badged" android:layout_marginLeft="@dimen/conversation_badge_side_margin" android:layout_marginTop="@dimen/conversation_badge_side_margin" + android:clipChildren="false" + android:clipToPadding="false" > <com.android.internal.widget.CachingIconView android:id="@+id/conversation_icon_badge_bg" android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_gravity="center" android:src="@drawable/conversation_badge_background" android:forceHasOverlappingRendering="false" + android:scaleType="center" /> <com.android.internal.widget.CachingIconView android:id="@+id/icon" @@ -81,11 +87,14 @@ /> <com.android.internal.widget.CachingIconView android:id="@+id/conversation_icon_badge_ring" - android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" android:src="@drawable/conversation_badge_ring" android:visibility="gone" android:forceHasOverlappingRendering="false" + android:clipToPadding="false" + android:scaleType="center" /> </FrameLayout> </FrameLayout> @@ -132,7 +141,7 @@ android:id="@+id/conversation_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin" android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" android:textSize="16sp" android:singleLine="true" @@ -145,8 +154,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?attr/notificationHeaderTextAppearance" - android:layout_marginStart="@dimen/notification_header_separating_margin" - android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin" android:text="@string/notification_header_divider_symbol" android:layout_gravity="center" android:paddingTop="1sp" @@ -161,7 +170,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin" android:paddingTop="1sp" android:singleLine="true" /> @@ -171,8 +181,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?attr/notificationHeaderTextAppearance" - android:layout_marginStart="@dimen/notification_header_separating_margin" - android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" + android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin" android:text="@string/notification_header_divider_symbol" android:layout_gravity="center" android:paddingTop="1sp" @@ -186,7 +196,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:layout_marginStart="@dimen/notification_header_separating_margin" + android:layout_marginStart="@dimen/notification_conversation_header_separating_margin" android:paddingTop="1sp" android:showRelative="true" android:singleLine="true" diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 1a8c129af06f..8272fed8cc97 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -1786,7 +1786,7 @@ <string name="managed_profile_label_badge" msgid="6762559569999499495">"<xliff:g id="LABEL">%1$s</xliff:g> do traballo"</string> <string name="managed_profile_label_badge_2" msgid="5673187309555352550">"2.º <xliff:g id="LABEL">%1$s</xliff:g> do traballo"</string> <string name="managed_profile_label_badge_3" msgid="6882151970556391957">"3.º <xliff:g id="LABEL">%1$s</xliff:g> do traballo"</string> - <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Solicitar PIN para soltar fixación"</string> + <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Solicitar PIN para deixar de fixar"</string> <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Solicitar un padrón de desbloqueo antes de deixar de fixar a pantalla"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Solicitar un contrasinal para deixar de fixar a pantalla"</string> <string name="package_installed_device_owner" msgid="7035926868974878525">"Instalado polo teu administrador"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index 2877e8009407..11683ae46138 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1786,7 +1786,7 @@ <string name="managed_profile_label_badge" msgid="6762559569999499495">"Ажлын <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="managed_profile_label_badge_2" msgid="5673187309555352550">"2 дахь ажил <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="managed_profile_label_badge_3" msgid="6882151970556391957">"3 дахь ажил <xliff:g id="LABEL">%1$s</xliff:g>"</string> - <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Тогтоосныг суллахаас өмнө PIN асуух"</string> + <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Бэхэлснийг болиулахаасаа өмнө PIN асуух"</string> <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Тогтоосныг суллахаас өмнө түгжээ тайлах хээ асуух"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Тогтоосныг суллахаас өмнө нууц үг асуух"</string> <string name="package_installed_device_owner" msgid="7035926868974878525">"Таны админ суулгасан"</string> @@ -1841,7 +1841,7 @@ <string name="zen_mode_default_weeknights_name" msgid="7902108149994062847">"Ажлын өдрийн шөнө"</string> <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"Амралтын өдөр"</string> <string name="zen_mode_default_events_name" msgid="2280682960128512257">"Үйл явдал"</string> - <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Идэвхгүй"</string> + <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"Унтлагын цаг"</string> <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> зарим дууны дууг хааж байна"</string> <string name="system_error_wipe_data" msgid="5910572292172208493">"Таны төхөөрөмжид дотоод алдаа байна.Та төхөөрөмжөө үйлдвэрээс гарсан төлөвт шилжүүлэх хүртэл таны төхөөрөмж чинь тогтворгүй байж болох юм."</string> <string name="system_error_manufacturer" msgid="703545241070116315">"Таны төхөөрөмжид дотоод алдаа байна. Дэлгэрэнгүй мэдээлэл авахыг хүсвэл үйлдвэрлэгчтэйгээ холбоо барина уу."</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 1f72b8b83315..d910fc03e165 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -1551,7 +1551,7 @@ <string name="activity_resolver_use_once" msgid="948462794469672658">"တစ်ခါတည်း"</string> <string name="activity_resolver_work_profiles_support" msgid="4071345609235361269">"%1$s က အလုပ်ပရိုဖိုင်ကို မပံ့ပိုးပါ။"</string> <string name="default_audio_route_name" product="tablet" msgid="367936735632195517">"တက်ဘလက်"</string> - <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"တီဗွီ"</string> + <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"TV"</string> <string name="default_audio_route_name" product="default" msgid="9213546147739983977">"ဖုန်း"</string> <string name="default_audio_route_name_dock_speakers" msgid="1551166029093995289">"အထိုင်ရှိသော စပီကာများ"</string> <string name="default_audio_route_name_hdmi" msgid="5474470558160717850">"HDMI"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 1739453fe0b8..b1c4fbbf0b09 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -951,11 +951,11 @@ <string name="autofill_area" msgid="8289022370678448983">"Gebied"</string> <string name="autofill_emirate" msgid="2544082046790551168">"Emiraat"</string> <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"je webbladwijzers en -geschiedenis lezen"</string> - <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Hiermee kan de app de geschiedenis lezen van alle URL\'s die in de systeemeigen browser zijn bezocht, en alle bladwijzers in de systeemeigen browser. Let op: deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string> + <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Hiermee kan de app de geschiedenis lezen van alle URL\'s die in de systeemeigen browser zijn bezocht, en alle bookmarks in de systeemeigen browser. Let op: deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string> <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"webbladwijzers en -geschiedenis schrijven"</string> - <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bladwijzers die zijn opgeslagen op je tablet. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden.."</string> - <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Hiermee kan de app de browsergeschiedenis of opgeslagen bladwijzers bewerken op je Android TV-apparaat. Hierdoor kan de app mogelijk browsergegevens wissen of aanpassen. Opmerking: Dit recht kan niet worden afgedwongen door andere browsers of andere apps met internetmogelijkheden."</string> - <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bladwijzers die zijn opgeslagen op je telefoon. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string> + <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bookmarks die zijn opgeslagen op je tablet. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden.."</string> + <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Hiermee kan de app de browsergeschiedenis of opgeslagen bookmarks bewerken op je Android TV-apparaat. Hierdoor kan de app mogelijk browsergegevens wissen of aanpassen. Opmerking: Dit recht kan niet worden afgedwongen door andere browsers of andere apps met internetmogelijkheden."</string> + <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bookmarks die zijn opgeslagen op je telefoon. Deze toestemming kan niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string> <string name="permlab_setAlarm" msgid="1158001610254173567">"een wekker instellen"</string> <string name="permdesc_setAlarm" msgid="2185033720060109640">"Hiermee kan de app een wekker instellen in een geïnstalleerde wekker-app. Deze functie wordt door sommige wekker-apps niet geïmplementeerd."</string> <string name="permlab_addVoicemail" msgid="4770245808840814471">"voicemail toevoegen"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 4224523a5333..5986e7652fa3 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -787,7 +787,7 @@ <string name="imProtocolYahoo" msgid="5373338758093392231">"Yahoo"</string> <string name="imProtocolSkype" msgid="1486297589164830043">"Skype"</string> <string name="imProtocolQq" msgid="7254708777029006592">"QQ"</string> - <string name="imProtocolGoogleTalk" msgid="9194016024343166782">"ହ୍ୟାଙ୍ଗଆଉଟ୍ସ"</string> + <string name="imProtocolGoogleTalk" msgid="9194016024343166782">"Hangouts"</string> <string name="imProtocolIcq" msgid="2410325380427389521">"ICQ"</string> <string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string> <string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 59bb052cbdf5..4ee919289f28 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -273,6 +273,9 @@ <!-- The margin before and after each of the items in the notification header. --> <dimen name="notification_header_separating_margin">2dp</dimen> + <!-- The margin before and after each of the items in the conversation header. --> + <dimen name="notification_conversation_header_separating_margin">4dp</dimen> + <!-- The absolute size of the notification expand icon. -2 for wrap_content. --> <dimen name="notification_header_expand_icon_size">-2px</dimen> @@ -719,10 +722,16 @@ <!-- The width of the protection of the face pile layout when expanded--> <dimen name="conversation_face_pile_protection_width_expanded">1dp</dimen> <!-- The padding of the expanded message container--> - <dimen name="expanded_group_conversation_message_padding">14dp</dimen> + <dimen name="expanded_group_conversation_message_padding">17dp</dimen> + <!-- The stroke width of the ring used to visually mark a conversation as important --> + <dimen name="importance_ring_stroke_width">2dp</dimen> + <!-- The maximum stroke width used for the animation shown when a conversation is marked as important --> + <dimen name="importance_ring_anim_max_stroke_width">10dp</dimen> + <!-- The size of the importance ring --> + <dimen name="importance_ring_size">20dp</dimen> <!-- The top padding of the conversation icon container in the regular state--> - <dimen name="conversation_icon_container_top_padding">9dp</dimen> + <dimen name="conversation_icon_container_top_padding">12dp</dimen> <!-- The top padding of the conversation icon container when the avatar is small--> <dimen name="conversation_icon_container_top_padding_small_avatar">17.5dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ab4005b4893c..c0cb39a56dd6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3903,6 +3903,12 @@ <java-symbol type="array" name="config_defaultImperceptibleKillingExemptionPkgs" /> <java-symbol type="array" name="config_defaultImperceptibleKillingExemptionProcStates" /> + <java-symbol type="color" name="conversation_important_highlight" /> + <java-symbol type="dimen" name="importance_ring_stroke_width" /> + <java-symbol type="dimen" name="importance_ring_anim_max_stroke_width" /> + <java-symbol type="dimen" name="importance_ring_size" /> + <java-symbol type="dimen" name="conversation_icon_size_badged" /> + <java-symbol type="id" name="header_icon_container" /> <java-symbol type="attr" name="notificationHeaderTextAppearance" /> <java-symbol type="string" name="conversation_single_line_name_display" /> @@ -3950,6 +3956,13 @@ <java-symbol type="id" name="conversation_unread_count" /> <java-symbol type="string" name="unread_convo_overflow" /> <java-symbol type="style" name="TextAppearance.DeviceDefault.Notification.Conversation.AppName" /> + <java-symbol type="drawable" name="conversation_badge_background" /> + <java-symbol type="drawable" name="conversation_badge_ring" /> + <java-symbol type="color" name="conversation_important_highlight" /> + <java-symbol type="dimen" name="importance_ring_stroke_width" /> + <java-symbol type="dimen" name="importance_ring_anim_max_stroke_width" /> + <java-symbol type="dimen" name="importance_ring_size" /> + <java-symbol type="dimen" name="conversation_icon_size_badged" /> <!-- Intent resolver and share sheet --> <java-symbol type="string" name="resolver_personal_tab" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 70917e76f8b4..d5733e34a8ef 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -39,7 +39,7 @@ <!-- Albania: 5 digits, known short codes listed --> <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> - <!-- Argentia: 5 digits, known short codes listed --> + <!-- Argentina: 5 digits, known short codes listed --> <shortcode country="ar" pattern="\\d{5}" free="11711|28291" /> <!-- Armenia: 3-4 digits, emergency numbers 10[123] --> @@ -70,7 +70,7 @@ <shortcode country="by" pattern="\\d{4}" premium="3336|4161|444[4689]|501[34]|7781" /> <!-- Canada: 5-6 digits --> - <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" standard="244444" /> + <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" standard="244444" free="455677" /> <!-- Switzerland: 3-5 digits: http://www.swisscom.ch/fxres/kmu/thirdpartybusiness_code_of_conduct_en.pdf --> <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765|30075|30047" /> diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml index 17e1f2e0c229..625558406b9c 100644 --- a/data/etc/preinstalled-packages-platform.xml +++ b/data/etc/preinstalled-packages-platform.xml @@ -17,7 +17,9 @@ <!-- This XML file declares which system packages should be initially installed for new users based on their user type. All system packages on the device should ideally have an entry in an xml file -(keyed by its manifest name). +(keyed by its manifest name), except auto-generated rro packages. Auto-generated RRO packages +(package name ends with ".auto_generated_rro_product__" or ".auto_generated_rro_vendor__") +will be installed for new users according to corresponding overlay target packages. Base user-types (every user will be at least one of these types) are: SYSTEM (user 0) @@ -51,7 +53,7 @@ The following three examples should cover most normal cases: 2. For a system package to be pre-installed on all human users (e.g. a web browser), i.e. to be -installed on any user of type type FULL or PROFILE (since this covers all human users): +installed on any user of type FULL or PROFILE (since this covers all human users): <install-in-user-type package="com.android.example"> <install-in user-type="FULL" /> diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java index 9846436b3ac8..67a040dba3e7 100644 --- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java +++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java @@ -18,7 +18,6 @@ package com.android.internal.location; import android.app.Notification; import android.app.NotificationManager; -import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.Context; @@ -402,13 +401,9 @@ public class GpsNetInitiatedHandler { mNiNotificationBuilder.setDefaults(0); } - // if not to popup dialog immediately, pending intent will open the dialog - Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent(); - PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); mNiNotificationBuilder.setTicker(getNotifTicker(notif, mContext)) .setContentTitle(title) - .setContentText(message) - .setContentIntent(pi); + .setContentText(message); notificationManager.notifyAsUser(null, notif.notificationId, mNiNotificationBuilder.build(), UserHandle.ALL); diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index a03b24cbbcf6..f970b598d6f8 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -233,10 +233,12 @@ void JMediaCodec::release() { } void JMediaCodec::releaseAsync() { - if (mCodec != NULL) { - mCodec->releaseAsync(); - } - mInitStatus = NO_INIT; + std::call_once(mAsyncReleaseFlag, [this] { + if (mCodec != NULL) { + mCodec->releaseAsync(new AMessage(kWhatAsyncReleaseComplete, this)); + } + mInitStatus = NO_INIT; + }); } JMediaCodec::~JMediaCodec() { @@ -1084,6 +1086,12 @@ void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) { handleFrameRenderedNotification(msg); break; } + case kWhatAsyncReleaseComplete: + { + mCodec.clear(); + mLooper->stop(); + break; + } default: TRESPASS(); } diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 5c34341a86a1..a58f9a74b563 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -173,6 +173,7 @@ private: enum { kWhatCallbackNotify, kWhatFrameRendered, + kWhatAsyncReleaseComplete, }; jclass mClass; @@ -185,6 +186,7 @@ private: bool mGraphicOutput{false}; bool mHasCryptoOrDescrambler{false}; std::once_flag mReleaseFlag; + std::once_flag mAsyncReleaseFlag; sp<AMessage> mCallbackNotification; sp<AMessage> mOnFrameRenderedNotification; diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 8f919c3d86ca..132922a59fc1 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -520,9 +520,11 @@ public class ExternalStorageProvider extends FileSystemProvider { final RootInfo root = resolvedDocId.first; File child = resolvedDocId.second; + final File rootFile = root.visiblePath != null ? root.visiblePath + : root.path; final File parent = TextUtils.isEmpty(parentDocId) - ? root.path - : getFileForDocId(parentDocId); + ? rootFile + : getFileForDocId(parentDocId); return new Path(parentDocId == null ? root.rootId : null, findDocumentPath(parent, child)); } diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS index a28ba8584054..d7bd6a49d75b 100644 --- a/packages/SettingsLib/OWNERS +++ b/packages/SettingsLib/OWNERS @@ -3,6 +3,7 @@ dsandler@android.com edgarwang@google.com emilychuang@google.com evanlaird@google.com +juliacr@google.com leifhendrik@google.com rafftsai@google.com tmfang@google.com diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java index d5f1ece5f83f..549bc8a455cf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java @@ -86,7 +86,7 @@ public class ConversationIconFactory extends BaseIconFactory { /** * Returns the conversation info drawable */ - private Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) { + public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) { return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi); } @@ -94,7 +94,7 @@ public class ConversationIconFactory extends BaseIconFactory { * Get the {@link Drawable} that represents the app icon, badged with the work profile icon * if appropriate. */ - private Drawable getAppBadge(String packageName, int userId) { + public Drawable getAppBadge(String packageName, int userId) { Drawable badge = null; try { final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index d1cd043352cb..3015397ff1a3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -900,9 +900,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro updateNetworkInfo(info); fetchScansAndConfigsAndUpdateAccessPoints(); } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { - NetworkInfo info = - mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork()); - updateNetworkInfo(info); + updateNetworkInfo(/* networkInfo= */ null); } } }; @@ -948,7 +946,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro // We don't send a NetworkInfo object along with this message, because even if we // fetch one from ConnectivityManager, it might be older than the most recent // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast. - updateNetworkInfo(null); + updateNetworkInfo(/* networkInfo= */ null); } } } diff --git a/packages/SystemUI/res/layout/controls_base_item.xml b/packages/SystemUI/res/layout/controls_base_item.xml index 477a70f4c7ad..5f83f45958e9 100644 --- a/packages/SystemUI/res/layout/controls_base_item.xml +++ b/packages/SystemUI/res/layout/controls_base_item.xml @@ -52,6 +52,7 @@ android:singleLine="true" android:ellipsize="marquee" android:marqueeRepeatLimit = "marquee_forever" + android:textDirection="locale" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@+id/icon" app:layout_constraintStart_toEndOf="@+id/icon" /> @@ -67,6 +68,7 @@ android:focusable="false" android:maxLines="1" android:ellipsize="end" + android:textDirection="locale" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@id/barrier"/> @@ -90,6 +92,7 @@ android:focusable="false" android:maxLines="1" android:ellipsize="end" + android:textDirection="locale" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/favorite" app:layout_constraintTop_toTopOf="@id/favorite" diff --git a/packages/SystemUI/res/layout/controls_more_item.xml b/packages/SystemUI/res/layout/controls_more_item.xml index df03787d567c..da9c43ccc1e9 100644 --- a/packages/SystemUI/res/layout/controls_more_item.xml +++ b/packages/SystemUI/res/layout/controls_more_item.xml @@ -20,5 +20,6 @@ android:layout_height="wrap_content" android:layout_gravity="start" android:paddingStart="@dimen/control_menu_horizontal_padding" - android:paddingEnd="@dimen/control_menu_horizontal_padding"/> + android:paddingEnd="@dimen/control_menu_horizontal_padding" + android:textDirection="locale"/> diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml index 803b0c61e6da..ecc311893497 100644 --- a/packages/SystemUI/res/layout/partial_conversation_info.xml +++ b/packages/SystemUI/res/layout/partial_conversation_info.xml @@ -35,7 +35,7 @@ android:clipChildren="false" android:clipToPadding="false"> <ImageView - android:id="@+id/conversation_icon" + android:id="@+id/icon" android:layout_width="@dimen/notification_guts_conversation_icon_size" android:layout_height="@dimen/notification_guts_conversation_icon_size" android:layout_centerVertical="true" @@ -60,20 +60,6 @@ android:textDirection="locale" style="@style/TextAppearance.NotificationImportanceChannel"/> <TextView - android:id="@+id/parent_channel_name" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:ellipsize="end" - android:textDirection="locale" - style="@style/TextAppearance.NotificationImportanceChannel"/> - <TextView - android:id="@+id/group_name" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:ellipsize="end" - android:textDirection="locale" - style="@style/TextAppearance.NotificationImportanceChannelGroup"/> - <TextView android:id="@+id/delegate_name" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml index bf2eac3c8ff3..3f0e514a9af2 100644 --- a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml +++ b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml @@ -38,11 +38,61 @@ android:background="@drawable/rounded_bg_full" > + <FrameLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" + android:padding="12dp" + android:layout_gravity="center_horizontal" + > + + <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right --> <ImageView android:id="@+id/conversation_icon" - android:layout_width="@dimen/notification_guts_conversation_icon_size" - android:layout_height="@dimen/notification_guts_conversation_icon_size" - android:layout_gravity="center_horizontal" /> + android:layout_width="@*android:dimen/conversation_avatar_size" + android:layout_height="@*android:dimen/conversation_avatar_size" + android:scaleType="centerCrop" + android:importantForAccessibility="no" + /> + + <FrameLayout + android:id="@+id/conversation_icon_badge" + android:layout_width="@*android:dimen/conversation_icon_size_badged" + android:layout_height="@*android:dimen/conversation_icon_size_badged" + android:layout_marginLeft="@*android:dimen/conversation_badge_side_margin" + android:layout_marginTop="@*android:dimen/conversation_badge_side_margin" + android:clipChildren="false" + android:clipToPadding="false" + > + <ImageView + android:id="@+id/conversation_icon_badge_bg" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center" + android:src="@*android:drawable/conversation_badge_background" + android:forceHasOverlappingRendering="false" + /> + <ImageView + android:id="@+id/icon" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="4dp" + android:layout_gravity="center" + android:forceHasOverlappingRendering="false" + /> + <ImageView + android:id="@+id/conversation_icon_badge_ring" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:src="@*android:drawable/conversation_badge_ring" + android:forceHasOverlappingRendering="false" + android:clipToPadding="false" + android:scaleType="center" + /> + </FrameLayout> + </FrameLayout> <TextView android:id="@+id/title" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 73d8e9a0d8a7..399099702a6a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1222,10 +1222,6 @@ <dimen name="bubble_dismiss_slop">16dp</dimen> <!-- Height of button allowing users to adjust settings for bubbles. --> <dimen name="bubble_manage_button_height">48dp</dimen> - <!-- How far, horizontally, to animate the expanded view over when animating in/out. --> - <dimen name="bubble_expanded_animate_x_distance">100dp</dimen> - <!-- How far, vertically, to animate the expanded view over when animating in/out. --> - <dimen name="bubble_expanded_animate_y_distance">500dp</dimen> <!-- Max width of the message bubble--> <dimen name="bubble_message_max_width">144dp</dimen> <!-- Min width of the message bubble --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 820615a6098d..48ff5c681853 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1256,7 +1256,7 @@ <string name="manage_notifications_history_text">History</string> <!-- Section title for notifications that have recently appeared. [CHAR LIMIT=40] --> - <string name="notification_section_header_incoming">Incoming</string> + <string name="notification_section_header_incoming">New</string> <!-- Section title for notifications that do not vibrate or make noise. [CHAR LIMIT=40] --> <string name="notification_section_header_gentle">Silent</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 9d8c54501c02..f639c880c97a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -141,14 +141,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mLayoutTransition.setAnimateParentHierarchy(false); } - // Temporary workaround to allow KeyguardStatusView to inflate a copy for Universal Smartspace. - // Eventually the existing copy will be reparented instead, and we won't need this. - public KeyguardSliceView(Context context, AttributeSet attributeSet) { - this(context, attributeSet, Dependency.get(ActivityStarter.class), - Dependency.get(ConfigurationController.class), Dependency.get(TunerService.class), - context.getResources()); - } - @Override protected void onFinishInflate() { super.onFinishInflate(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index a181ce4b000a..5a1c9976f021 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -18,10 +18,7 @@ package com.android.keyguard; import android.app.ActivityManager; import android.app.IActivityManager; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.Color; import android.os.Handler; @@ -43,8 +40,6 @@ import androidx.core.graphics.ColorUtils; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.shared.system.SurfaceViewRequestReceiver; -import com.android.systemui.shared.system.UniversalSmartspaceUtils; import com.android.systemui.statusbar.policy.ConfigurationController; import java.io.FileDescriptor; @@ -127,21 +122,6 @@ public class KeyguardStatusView extends GridLayout implements } }; - private final BroadcastReceiver mUniversalSmartspaceBroadcastReceiver = - new BroadcastReceiver() { - private final SurfaceViewRequestReceiver mReceiver = new SurfaceViewRequestReceiver(); - - @Override - public void onReceive(Context context, Intent i) { - // TODO(b/148159743): Restrict to Pixel Launcher. - if (UniversalSmartspaceUtils.ACTION_REQUEST_SMARTSPACE_VIEW.equals(i.getAction())) { - mReceiver.onReceive(context, - i.getBundleExtra(UniversalSmartspaceUtils.INTENT_BUNDLE_KEY), - inflate(mContext, R.layout.keyguard_status_area, null)); - } - } - }; - public KeyguardStatusView(Context context) { this(context, null, 0); } @@ -336,8 +316,6 @@ public class KeyguardStatusView extends GridLayout implements super.onAttachedToWindow(); Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback); Dependency.get(ConfigurationController.class).addCallback(this); - getContext().registerReceiver(mUniversalSmartspaceBroadcastReceiver, - new IntentFilter(UniversalSmartspaceUtils.ACTION_REQUEST_SMARTSPACE_VIEW)); } @Override @@ -345,7 +323,6 @@ public class KeyguardStatusView extends GridLayout implements super.onDetachedFromWindow(); Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback); Dependency.get(ConfigurationController.class).removeCallback(this); - getContext().unregisterReceiver(mUniversalSmartspaceBroadcastReceiver); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index ccb506de6d8b..0218cd237037 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -124,7 +124,7 @@ public final class Prefs { String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding"; String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount"; /** Tracks whether the user has seen the onboarding screen for priority conversations */ - String HAS_SEEN_PRIORITY_ONBOARDING = "HaveShownPriorityOnboarding"; + String HAS_SEEN_PRIORITY_ONBOARDING = "HasUserSeenPriorityOnboarding"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 97a73043aa08..7f78ddf2cf1c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -303,10 +303,10 @@ class Bubble implements BubbleViewProvider { mDotPath = info.dotPath; if (mExpandedView != null) { - mExpandedView.update(/* bubble */ this); + mExpandedView.update(this /* bubble */); } if (mIconView != null) { - mIconView.setRenderedBubble(/* bubble */ this); + mIconView.setRenderedBubble(this /* bubble */); } } @@ -548,13 +548,13 @@ class Bubble implements BubbleViewProvider { } private boolean shouldSuppressNotification() { - if (mEntry == null) return false; + if (mEntry == null) return true; return mEntry.getBubbleMetadata() != null && mEntry.getBubbleMetadata().isNotificationSuppressed(); } boolean shouldAutoExpand() { - if (mEntry == null) return false; + if (mEntry == null) return mShouldAutoExpand; Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand; } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index cf793f021a29..35cac4d9d63d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -156,6 +156,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private final ShadeController mShadeController; private final FloatingContentCoordinator mFloatingContentCoordinator; private final BubbleDataRepository mDataRepository; + private BubbleLogger mLogger = new BubbleLoggerImpl(); private BubbleData mBubbleData; private ScrimView mBubbleScrim; @@ -317,6 +318,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi BubbleDataRepository dataRepository, SysUiState sysUiState, INotificationManager notificationManager, + @Nullable IStatusBarService statusBarService, WindowManager windowManager) { dumpManager.registerDumpable(TAG, this); mContext = context; @@ -387,8 +389,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mSurfaceSynchronizer = synchronizer; mWindowManager = windowManager; - mBarService = IStatusBarService.Stub.asInterface( - ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mBarService = statusBarService == null + ? IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)) + : statusBarService; mBubbleScrim = new ScrimView(mContext); @@ -617,7 +621,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (mStackView == null) { mStackView = new BubbleStackView( mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator, - mSysUiState, mNotificationShadeWindowController, this::onAllBubblesAnimatedOut, + mSysUiState, this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged); mStackView.addView(mBubbleScrim); if (mExpandListener != null) { @@ -894,9 +898,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } void promoteBubbleFromOverflow(Bubble bubble) { + mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); bubble.setInflateSynchronously(mInflateSynchronously); - setIsBubble(bubble, /* isBubble */ true); - mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory); + bubble.setShouldAutoExpand(true); + bubble.markUpdatedAt(System.currentTimeMillis()); + setIsBubble(bubble, true /* isBubble */); } /** @@ -910,20 +916,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi Bubble bubble = mBubbleData.getBubbleInStackWithKey(key); if (bubble != null) { mBubbleData.setSelectedBubble(bubble); + mBubbleData.setExpanded(true); } else { bubble = mBubbleData.getOverflowBubbleWithKey(key); if (bubble != null) { - bubble.setShouldAutoExpand(true); promoteBubbleFromOverflow(bubble); } else if (entry.canBubble()) { // It can bubble but it's not -- it got aged out of the overflow before it // was dismissed or opened, make it a bubble again. - setIsBubble(entry, true); - updateBubble(entry, true /* suppressFlyout */, false /* showInShade */); + setIsBubble(entry, true /* isBubble */, true /* autoExpand */); } } - - mBubbleData.setExpanded(true); } /** @@ -967,13 +970,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) { - // Lazy init stack view when a bubble is created - ensureStackViewCreated(); // If this is an interruptive notif, mark that it's interrupted if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) { notif.setInterruption(); } - Bubble bubble = mBubbleData.getOrCreateBubble(notif); + Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); + inflateAndAdd(bubble, suppressFlyout, showInShade); + } + + void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) { + // Lazy init stack view when a bubble is created + ensureStackViewCreated(); bubble.setInflateSynchronously(mInflateSynchronously); bubble.inflate( b -> { @@ -1119,7 +1126,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } - private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble) { + private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble, + final boolean autoExpand) { Objects.requireNonNull(entry); if (isBubble) { entry.getSbn().getNotification().flags |= FLAG_BUBBLE; @@ -1127,7 +1135,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi entry.getSbn().getNotification().flags &= ~FLAG_BUBBLE; } try { - mBarService.onNotificationBubbleChanged(entry.getKey(), isBubble, 0); + int flags = 0; + if (autoExpand) { + flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE; + } + mBarService.onNotificationBubbleChanged(entry.getKey(), isBubble, flags); } catch (RemoteException e) { // Bad things have happened } @@ -1141,13 +1154,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi b.disable(FLAG_BUBBLE); } if (b.getEntry() != null) { - setIsBubble(b.getEntry(), isBubble); + // Updating the entry to be a bubble will trigger our normal update flow + setIsBubble(b.getEntry(), isBubble, b.shouldAutoExpand()); } else { - try { - mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0); - } catch (RemoteException e) { - // Bad things have happened - } + // If we have no entry to update, it's a persisted bubble so + // we need to add it to the stack ourselves + Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */); + inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */, + !bubble.shouldAutoExpand() /* showInShade */); + } } @@ -1199,7 +1214,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } } else { - if (bubble.isBubble() && bubble.showInShade()) { + if (bubble.isBubble()) { setIsBubble(bubble, false /* isBubble */); } if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 996a5553b5b2..24d44d5cb291 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -115,7 +115,7 @@ public class BubbleData { /** Bubbles that aged out to overflow. */ private final List<Bubble> mOverflowBubbles; /** Bubbles that are being loaded but haven't been added to the stack just yet. */ - private final List<Bubble> mPendingBubbles; + private final HashMap<String, Bubble> mPendingBubbles; private Bubble mSelectedBubble; private boolean mShowingOverflow; private boolean mExpanded; @@ -151,7 +151,7 @@ public class BubbleData { mContext = context; mBubbles = new ArrayList<>(); mOverflowBubbles = new ArrayList<>(); - mPendingBubbles = new ArrayList<>(); + mPendingBubbles = new HashMap<>(); mStateChange = new Update(mBubbles, mOverflowBubbles); mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered); mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow); @@ -203,62 +203,46 @@ public class BubbleData { dispatchPendingChanges(); } - public void promoteBubbleFromOverflow(Bubble bubble, BubbleStackView stack, - BubbleIconFactory factory) { - if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "promoteBubbleFromOverflow: " + bubble); - } - mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); - moveOverflowBubbleToPending(bubble); - // Preserve new order for next repack, which sorts by last updated time. - bubble.inflate( - b -> { - b.setShouldAutoExpand(true); - b.markUpdatedAt(mTimeSource.currentTimeMillis()); - notificationEntryUpdated(bubble, false /* suppressFlyout */, - true /* showInShade */); - }, - mContext, stack, factory, false /* skipInflation */); - } - void setShowingOverflow(boolean showingOverflow) { mShowingOverflow = showingOverflow; } - private void moveOverflowBubbleToPending(Bubble b) { - mOverflowBubbles.remove(b); - mPendingBubbles.add(b); - } - /** * Constructs a new bubble or returns an existing one. Does not add new bubbles to * bubble data, must go through {@link #notificationEntryUpdated(Bubble, boolean, boolean)} * for that. + * + * @param entry The notification entry to use, only null if it's a bubble being promoted from + * the overflow that was persisted over reboot. + * @param persistedBubble The bubble to use, only non-null if it's a bubble being promoted from + * the overflow that was persisted over reboot. */ - Bubble getOrCreateBubble(NotificationEntry entry) { - String key = entry.getKey(); - Bubble bubble = getBubbleInStackWithKey(entry.getKey()); - if (bubble != null) { - bubble.setEntry(entry); - } else { - bubble = getOverflowBubbleWithKey(key); - if (bubble != null) { - moveOverflowBubbleToPending(bubble); - bubble.setEntry(entry); - return bubble; - } - // Check for it in pending - for (int i = 0; i < mPendingBubbles.size(); i++) { - Bubble b = mPendingBubbles.get(i); - if (b.getKey().equals(entry.getKey())) { - b.setEntry(entry); - return b; - } + Bubble getOrCreateBubble(NotificationEntry entry, Bubble persistedBubble) { + String key = entry != null ? entry.getKey() : persistedBubble.getKey(); + Bubble bubbleToReturn = getBubbleInStackWithKey(key); + + if (bubbleToReturn == null) { + bubbleToReturn = getOverflowBubbleWithKey(key); + if (bubbleToReturn != null) { + // Promoting from overflow + mOverflowBubbles.remove(bubbleToReturn); + } else if (mPendingBubbles.containsKey(key)) { + // Update while it was pending + bubbleToReturn = mPendingBubbles.get(key); + } else if (entry != null) { + // New bubble + bubbleToReturn = new Bubble(entry, mSuppressionListener); + } else { + // Persisted bubble being promoted + bubbleToReturn = persistedBubble; } - bubble = new Bubble(entry, mSuppressionListener); - mPendingBubbles.add(bubble); } - return bubble; + + if (entry != null) { + bubbleToReturn.setEntry(entry); + } + mPendingBubbles.put(key, bubbleToReturn); + return bubbleToReturn; } /** @@ -270,7 +254,7 @@ public class BubbleData { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "notificationEntryUpdated: " + bubble); } - mPendingBubbles.remove(bubble); // No longer pending once we're here + mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); suppressFlyout |= bubble.getEntry() == null || !bubble.getEntry().getRanking().visuallyInterruptive(); @@ -408,10 +392,8 @@ public class BubbleData { Log.d(TAG, "doRemove: " + key); } // If it was pending remove it - for (int i = 0; i < mPendingBubbles.size(); i++) { - if (mPendingBubbles.get(i).getKey().equals(key)) { - mPendingBubbles.remove(mPendingBubbles.get(i)); - } + if (mPendingBubbles.containsKey(key)) { + mPendingBubbles.remove(key); } int indexToRemove = indexForKey(key); if (indexToRemove == -1) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index c3dcc0b3038c..2fd4d2aaf1d7 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -22,6 +22,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.graphics.PixelFormat.TRANSPARENT; import static android.view.Display.INVALID_DISPLAY; import static android.view.InsetsState.ITYPE_IME; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.sNewInsetsMode; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; @@ -33,7 +34,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPAND import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityOptions; import android.app.ActivityTaskManager; @@ -46,6 +46,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Insets; +import android.graphics.Outline; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.ShapeDrawable; @@ -55,12 +56,19 @@ import android.os.RemoteException; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; +import android.view.SurfaceControl; +import android.view.SurfaceView; import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.FrameLayout; import android.widget.LinearLayout; +import androidx.annotation.Nullable; + import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -88,6 +96,7 @@ public class BubbleExpandedView extends LinearLayout { // The triangle pointing to the expanded view private View mPointerView; private int mPointerMargin; + @Nullable private int[] mExpandedViewContainerLocation; private AlphaOptimizedButton mSettingsIcon; @@ -121,6 +130,16 @@ public class BubbleExpandedView extends LinearLayout { private View mVirtualImeView; private WindowManager mVirtualDisplayWindowManager; private boolean mImeShowing = false; + private float mCornerRadius = 0f; + + /** + * Container for the ActivityView that has a solid, round-rect background that shows if the + * ActivityView hasn't loaded. + */ + private FrameLayout mActivityViewContainer = new FrameLayout(getContext()); + + /** The SurfaceView that the ActivityView draws to. */ + @Nullable private SurfaceView mActivitySurface; private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() { @Override @@ -269,7 +288,28 @@ public class BubbleExpandedView extends LinearLayout { // Set ActivityView's alpha value as zero, since there is no view content to be shown. setContentVisibility(false); - addView(mActivityView); + + mActivityViewContainer.setBackgroundColor(Color.WHITE); + mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); + } + }); + mActivityViewContainer.setClipToOutline(true); + mActivityViewContainer.addView(mActivityView); + mActivityViewContainer.setLayoutParams( + new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + addView(mActivityViewContainer); + + if (mActivityView != null + && mActivityView.getChildCount() > 0 + && mActivityView.getChildAt(0) instanceof SurfaceView) { + // Retrieve the surface from the ActivityView so we can screenshot it and change its + // z-ordering. This should always be possible, since ActivityView's constructor adds the + // SurfaceView as its first child. + mActivitySurface = (SurfaceView) mActivityView.getChildAt(0); + } // Expanded stack layout, top to bottom: // Expanded view container @@ -327,6 +367,39 @@ public class BubbleExpandedView extends LinearLayout { return mBubble != null ? mBubble.getKey() : "null"; } + /** + * Asks the ActivityView's surface to draw on top of all other views in the window. This is + * useful for ordering surfaces during animations, but should otherwise be set to false so that + * bubbles and menus can draw over the ActivityView. + */ + void setSurfaceZOrderedOnTop(boolean onTop) { + if (mActivitySurface == null) { + return; + } + + mActivitySurface.setZOrderedOnTop(onTop, true); + } + + /** Return a GraphicBuffer with the contents of the ActivityView's underlying surface. */ + @Nullable SurfaceControl.ScreenshotGraphicBuffer snapshotActivitySurface() { + if (mActivitySurface == null) { + return null; + } + + return SurfaceControl.captureLayers( + mActivitySurface.getSurfaceControl(), + new Rect(0, 0, mActivityView.getWidth(), mActivityView.getHeight()), + 1 /* scale */); + } + + int[] getActivityViewLocationOnScreen() { + if (mActivityView != null) { + return mActivityView.getLocationOnScreen(); + } else { + return new int[]{0, 0}; + } + } + void setManageClickListener(OnClickListener manageClickListener) { findViewById(R.id.settings_button).setOnClickListener(manageClickListener); } @@ -345,12 +418,12 @@ public class BubbleExpandedView extends LinearLayout { void applyThemeAttrs() { final TypedArray ta = mContext.obtainStyledAttributes( new int[] {android.R.attr.dialogCornerRadius}); - float cornerRadius = ta.getDimensionPixelSize(0, 0); + mCornerRadius = ta.getDimensionPixelSize(0, 0); ta.recycle(); if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows( mContext.getResources())) { - mActivityView.setCornerRadius(cornerRadius); + mActivityView.setCornerRadius(mCornerRadius); } } @@ -398,6 +471,7 @@ public class BubbleExpandedView extends LinearLayout { mPointerView.setAlpha(alpha); if (mActivityView != null) { mActivityView.setAlpha(alpha); + mActivityView.bringToFront(); } } @@ -490,7 +564,7 @@ public class BubbleExpandedView extends LinearLayout { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null")); } - boolean isNew = mBubble == null; + boolean isNew = mBubble == null || didBackingContentChange(bubble); if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) { mBubble = bubble; mSettingsIcon.setContentDescription(getResources().getString( @@ -523,6 +597,12 @@ public class BubbleExpandedView extends LinearLayout { } } + private boolean didBackingContentChange(Bubble newBubble) { + boolean prevWasIntentBased = mBubble != null && mPendingIntent != null; + boolean newIsIntentBased = newBubble.getBubbleIntent() != null; + return prevWasIntentBased != newIsIntentBased; + } + /** * Lets activity view know it should be shown / populated with activity content. */ @@ -551,6 +631,11 @@ public class BubbleExpandedView extends LinearLayout { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()); } + + if (mExpandedViewContainerLocation == null) { + return; + } + if (usingActivityView()) { float desiredHeight = mOverflowHeight; if (!mIsOverflow) { @@ -558,7 +643,7 @@ public class BubbleExpandedView extends LinearLayout { } float height = Math.min(desiredHeight, getMaxExpandedHeight()); height = Math.max(height, mIsOverflow? mOverflowHeight : mMinHeight); - LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams(); + ViewGroup.LayoutParams lp = mActivityView.getLayoutParams(); mNeedsNewHeight = lp.height != height; if (!mKeyboardVisible) { // If the keyboard is visible... don't adjust the height because that will cause @@ -568,7 +653,8 @@ public class BubbleExpandedView extends LinearLayout { mNeedsNewHeight = false; } if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + " height=" + height + Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + + " height=" + height + " mNeedsNewHeight=" + mNeedsNewHeight); } } @@ -576,28 +662,40 @@ public class BubbleExpandedView extends LinearLayout { private int getMaxExpandedHeight() { mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize); - int[] windowLocation = mActivityView.getLocationOnScreen(); int bottomInset = getRootWindowInsets() != null ? getRootWindowInsets().getStableInsetBottom() : 0; - return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight + + return mDisplaySize.y + - mExpandedViewContainerLocation[1] + - getPaddingTop() + - getPaddingBottom() + - mSettingsIconHeight + - mPointerHeight - mPointerMargin - bottomInset; } /** * Update appearance of the expanded view being displayed. + * + * @param containerLocationOnScreen The location on-screen of the container the expanded view is + * added to. This allows us to calculate max height without + * waiting for layout. */ - public void updateView() { + public void updateView(int[] containerLocationOnScreen) { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "updateView: bubble=" + getBubbleKey()); } + + mExpandedViewContainerLocation = containerLocationOnScreen; + if (usingActivityView() && mActivityView.getVisibility() == VISIBLE && mActivityView.isAttachedToWindow()) { mActivityView.onLocationChanged(); + updateHeight(); } - updateHeight(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 95c8d08841df..84f3ba5df917 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -56,6 +56,8 @@ import android.view.DisplayCutout; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.SurfaceControl; +import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; @@ -79,10 +81,10 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ContrastColorUtil; -import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.bubbles.animation.AnimatableScaleMatrix; import com.android.systemui.bubbles.animation.ExpandedAnimationController; import com.android.systemui.bubbles.animation.PhysicsAnimationLayout; import com.android.systemui.bubbles.animation.StackAnimationController; @@ -148,6 +150,16 @@ public class BubbleStackView extends FrameLayout StackAnimationController.IME_ANIMATION_STIFFNESS, StackAnimationController.DEFAULT_BOUNCINESS); + private final PhysicsAnimator.SpringConfig mScaleInSpringConfig = + new PhysicsAnimator.SpringConfig(300f, 0.9f); + + private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig = + new PhysicsAnimator.SpringConfig(900f, 1f); + + private final PhysicsAnimator.SpringConfig mTranslateSpringConfig = + new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY); + /** * Interface to synchronize {@link View} state and the screen. * @@ -187,8 +199,6 @@ public class BubbleStackView extends FrameLayout private Point mDisplaySize; - private final SpringAnimation mExpandedViewXAnim; - private final SpringAnimation mExpandedViewYAnim; private final BubbleData mBubbleData; private final ValueAnimator mDesaturateAndDarkenAnimator; @@ -200,6 +210,24 @@ public class BubbleStackView extends FrameLayout private FrameLayout mExpandedViewContainer; + /** Matrix used to scale the expanded view container with a given pivot point. */ + private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix(); + + /** + * SurfaceView that we draw screenshots of animating-out bubbles into. This allows us to animate + * between bubble activities without needing both to be alive at the same time. + */ + private SurfaceView mAnimatingOutSurfaceView; + + /** Container for the animating-out SurfaceView. */ + private FrameLayout mAnimatingOutSurfaceContainer; + + /** + * Buffer containing a screenshot of the animating-out bubble. This is drawn into the + * SurfaceView during animations. + */ + private SurfaceControl.ScreenshotGraphicBuffer mAnimatingOutBubbleBuffer; + private BubbleFlyoutView mFlyout; /** Runnable that fades out the flyout and then sets it to GONE. */ private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */); @@ -231,8 +259,7 @@ public class BubbleStackView extends FrameLayout private int mBubblePaddingTop; private int mBubbleTouchPadding; private int mExpandedViewPadding; - private int mExpandedAnimateXDistance; - private int mExpandedAnimateYDistance; + private int mCornerRadius; private int mPointerHeight; private int mStatusBarHeight; private int mImeOffset; @@ -292,20 +319,6 @@ public class BubbleStackView extends FrameLayout private ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater = this::updateSystemGestureExcludeRects; - private ViewClippingUtil.ClippingParameters mClippingParameters = - new ViewClippingUtil.ClippingParameters() { - - @Override - public boolean shouldFinish(View view) { - return false; - } - - @Override - public boolean isClippingEnablingAllowed(View view) { - return !mIsExpanded; - } - }; - /** Float property that 'drags' the flyout. */ private final FloatPropertyCompat mFlyoutCollapseProperty = new FloatPropertyCompat("FlyoutCollapseSpring") { @@ -348,8 +361,6 @@ public class BubbleStackView extends FrameLayout @NonNull private final SurfaceSynchronizer mSurfaceSynchronizer; - private final NotificationShadeWindowController mNotificationShadeWindowController; - /** * Callback to run when the IME visibility changes - BubbleController uses this to update the * Bubbles window focusability flags with the WindowManager. @@ -682,7 +693,6 @@ public class BubbleStackView extends FrameLayout @Nullable SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, SysUiState sysUiState, - NotificationShadeWindowController notificationShadeWindowController, Runnable allBubblesAnimatedOutAction, Consumer<Boolean> onImeVisibilityChanged) { super(context); @@ -691,7 +701,6 @@ public class BubbleStackView extends FrameLayout mInflater = LayoutInflater.from(context); mSysUiState = sysUiState; - mNotificationShadeWindowController = notificationShadeWindowController; Resources res = getResources(); mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered); @@ -699,10 +708,6 @@ public class BubbleStackView extends FrameLayout mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding); - mExpandedAnimateXDistance = - res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance); - mExpandedAnimateYDistance = - res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance); mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); mStatusBarHeight = @@ -717,6 +722,11 @@ public class BubbleStackView extends FrameLayout mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); + final TypedArray ta = mContext.obtainStyledAttributes( + new int[] {android.R.attr.dialogCornerRadius}); + mCornerRadius = ta.getDimensionPixelSize(0, 0); + ta.recycle(); + final Runnable onBubbleAnimatedOut = () -> { if (getBubbleCount() == 0) { allBubblesAnimatedOutAction.run(); @@ -750,6 +760,24 @@ public class BubbleStackView extends FrameLayout mExpandedViewContainer.setClipChildren(false); addView(mExpandedViewContainer); + mAnimatingOutSurfaceContainer = new FrameLayout(getContext()); + mAnimatingOutSurfaceContainer.setLayoutParams( + new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + addView(mAnimatingOutSurfaceContainer); + + mAnimatingOutSurfaceView = new SurfaceView(getContext()); + mAnimatingOutSurfaceView.setUseAlpha(); + mAnimatingOutSurfaceView.setZOrderOnTop(true); + mAnimatingOutSurfaceView.setCornerRadius(mCornerRadius); + mAnimatingOutSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(0, 0)); + mAnimatingOutSurfaceContainer.addView(mAnimatingOutSurfaceView); + + mAnimatingOutSurfaceContainer.setPadding( + mExpandedViewPadding, + mExpandedViewPadding, + mExpandedViewPadding, + mExpandedViewPadding); + setUpManageMenu(); setUpFlyout(); @@ -795,26 +823,6 @@ public class BubbleStackView extends FrameLayout // MagnetizedObjects. mMagneticTarget = new MagnetizedObject.MagneticTarget(mDismissTargetCircle, dismissRadius); - mExpandedViewXAnim = - new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X); - mExpandedViewXAnim.setSpring( - new SpringForce() - .setStiffness(SpringForce.STIFFNESS_LOW) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); - - mExpandedViewYAnim = - new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y); - mExpandedViewYAnim.setSpring( - new SpringForce() - .setStiffness(SpringForce.STIFFNESS_LOW) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); - mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> { - if (mIsExpanded && mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().updateView(); - } - }); - setClipChildren(false); setFocusable(true); mBubbleContainer.bringToFront(); @@ -849,7 +857,7 @@ public class BubbleStackView extends FrameLayout if (mIsExpanded) { mExpandedViewContainer.setTranslationY(getExpandedViewY()); if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().updateView(); + mExpandedBubble.getExpandedView().updateView(getLocationOnScreen()); } } @@ -973,15 +981,10 @@ public class BubbleStackView extends FrameLayout PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig); - final TypedArray ta = mContext.obtainStyledAttributes( - new int[] {android.R.attr.dialogCornerRadius}); - final int menuCornerRadius = ta.getDimensionPixelSize(0, 0); - ta.recycle(); - mManageMenu.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius); + outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); } }); mManageMenu.setClipToOutline(true); @@ -1399,7 +1402,6 @@ public class BubbleStackView extends FrameLayout mBubbleContainer.addView(bubble.getIconView(), 0, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters); animateInFlyoutForBubble(bubble); requestUpdate(); logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__POSTED); @@ -1467,6 +1469,31 @@ public class BubbleStackView extends FrameLayout mBubbleData.setShowingOverflow(true); } + // If we're expanded, screenshot the currently expanded bubble (before expanding the newly + // selected bubble) so we can animate it out. + if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + // Before screenshotting, have the real ActivityView show on top of other surfaces + // so that the screenshot doesn't flicker on top of it. + mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true); + } + + try { + screenshotAnimatingOutBubbleIntoSurface((success) -> { + mAnimatingOutSurfaceContainer.setVisibility( + success ? View.VISIBLE : View.INVISIBLE); + showNewlySelectedBubble(bubbleToSelect); + }); + } catch (Exception e) { + showNewlySelectedBubble(bubbleToSelect); + e.printStackTrace(); + } + } else { + showNewlySelectedBubble(bubbleToSelect); + } + } + + private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) { final BubbleViewProvider previouslySelected = mExpandedBubble; mExpandedBubble = bubbleToSelect; updatePointerPosition(); @@ -1657,83 +1684,215 @@ public class BubbleStackView extends FrameLayout } private void beforeExpandedViewAnimation() { + mIsExpansionAnimating = true; hideFlyoutImmediate(); updateExpandedBubble(); updateExpandedView(); - mIsExpansionAnimating = true; } private void afterExpandedViewAnimation() { - updateExpandedView(); mIsExpansionAnimating = false; + updateExpandedView(); requestUpdate(); } + private void animateExpansion() { + mIsExpanded = true; + hideStackUserEducation(true /* fromExpansion */); + beforeExpandedViewAnimation(); + + mBubbleContainer.setActiveController(mExpandedAnimationController); + updateOverflowVisibility(); + updatePointerPosition(); + mExpandedAnimationController.expandFromStack(() -> { + afterExpandedViewAnimation(); + maybeShowManageEducation(true); + } /* after */); + + mExpandedViewContainer.setTranslationX(0); + mExpandedViewContainer.setTranslationY(getExpandedViewY()); + mExpandedViewContainer.setAlpha(1f); + + // X-value of the bubble we're expanding, once it's settled in its row. + final float bubbleWillBeAtX = + mExpandedAnimationController.getBubbleLeft( + mBubbleData.getBubbles().indexOf(mExpandedBubble)); + + // How far horizontally the bubble will be animating. We'll wait a bit longer for bubbles + // that are animating farther, so that the expanded view doesn't move as much. + final float horizontalDistanceAnimated = + Math.abs(bubbleWillBeAtX + - mStackAnimationController.getStackPosition().x); + + // Wait for the path animation target to reach its end, and add a small amount of extra time + // if the bubble is moving a lot horizontally. + long startDelay = 0L; + + // Should not happen since we lay out before expanding, but just in case... + if (getWidth() > 0) { + startDelay = (long) + (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION + + (horizontalDistanceAnimated / getWidth()) * 30); + } + + // Set the pivot point for the scale, so the expanded view animates out from the bubble. + mExpandedViewContainerMatrix.setScale( + 0f, 0f, + bubbleWillBeAtX + mBubbleSize / 2f, getExpandedViewY()); + mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); + + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); + } + + postDelayed(() -> PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .addUpdateListener((target, values) -> { + mExpandedViewContainerMatrix.postTranslate( + mExpandedBubble.getIconView().getTranslationX() + - bubbleWillBeAtX, + 0); + mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); + }) + .withEndActions(() -> { + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); + } + }) + .start(), startDelay); + + } + private void animateCollapse() { // Hide the menu if it's visible. showManageMenu(false); mIsExpanded = false; - final BubbleViewProvider previouslySelected = mExpandedBubble; - beforeExpandedViewAnimation(); - maybeShowManageEducation(false); - if (DEBUG_BUBBLE_STACK_VIEW) { - Log.d(TAG, "animateCollapse"); - Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(), - mExpandedBubble)); - } - updateOverflowVisibility(); mBubbleContainer.cancelAllAnimations(); - mExpandedAnimationController.collapseBackToStack( + + // If we were in the middle of swapping, the animating-out surface would have been scaling + // to zero - finish it off. + PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel(); + mAnimatingOutSurfaceContainer.setScaleX(0f); + mAnimatingOutSurfaceContainer.setScaleY(0f); + + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView().hideImeIfVisible(); + } + + final long startDelay = + (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f); + postDelayed(() -> mExpandedAnimationController.collapseBackToStack( mStackAnimationController.getStackPositionAlongNearestHorizontalEdge() /* collapseTo */, () -> { mBubbleContainer.setActiveController(mStackAnimationController); + }), startDelay); + + // We want to visually collapse into this bubble during the animation. + final View expandingFromBubble = mExpandedBubble.getIconView(); + + // X-value the bubble is animating from (back into the stack). + final float expandingFromBubbleAtX = + mExpandedAnimationController.getBubbleLeft( + mBubbleData.getBubbles().indexOf(mExpandedBubble)); + + // Set the pivot point. + mExpandedViewContainerMatrix.setScale( + 1f, 1f, + expandingFromBubbleAtX + mBubbleSize / 2f, + getExpandedViewY()); + + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, 0f, mScaleOutSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, 0f, mScaleOutSpringConfig) + .addUpdateListener((target, values) -> { + if (expandingFromBubble != null) { + // Follow the bubble as it translates! + mExpandedViewContainerMatrix.postTranslate( + expandingFromBubble.getTranslationX() + - expandingFromBubbleAtX, 0f); + } + + mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); + + // Hide early so we don't have a tiny little expanded view still visible at the + // end of the scale animation. + if (mExpandedViewContainerMatrix.getScaleX() < 0.05f) { + mExpandedViewContainer.setVisibility(View.INVISIBLE); + } + }) + .withEndActions(() -> { + final BubbleViewProvider previouslySelected = mExpandedBubble; + beforeExpandedViewAnimation(); + maybeShowManageEducation(false); + + if (DEBUG_BUBBLE_STACK_VIEW) { + Log.d(TAG, "animateCollapse"); + Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(), + mExpandedBubble)); + } + updateOverflowVisibility(); + afterExpandedViewAnimation(); previouslySelected.setContentVisibility(false); - }); - - mExpandedViewXAnim.animateToFinalPosition(getCollapsedX()); - mExpandedViewYAnim.animateToFinalPosition(getCollapsedY()); - mExpandedViewContainer.animate() - .setDuration(100) - .alpha(0f); + }) + .start(); } - private void animateExpansion() { - mIsExpanded = true; - hideStackUserEducation(true /* fromExpansion */); - beforeExpandedViewAnimation(); + private void animateSwitchBubbles() { + // The surface contains a screenshot of the animating out bubble, so we just need to animate + // it out (and then release the GraphicBuffer). + PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel(); + PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer) + .spring(DynamicAnimation.SCALE_X, 0f, mScaleOutSpringConfig) + .spring(DynamicAnimation.SCALE_Y, 0f, mScaleOutSpringConfig) + .spring(DynamicAnimation.TRANSLATION_Y, + mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize * 2, + mTranslateSpringConfig) + .withEndActions(this::releaseAnimatingOutBubbleBuffer) + .start(); - mBubbleContainer.setActiveController(mExpandedAnimationController); - updateOverflowVisibility(); - mExpandedAnimationController.expandFromStack(() -> { - updatePointerPosition(); - afterExpandedViewAnimation(); - maybeShowManageEducation(true); - } /* after */); + float expandingFromBubbleDestinationX = + mExpandedAnimationController.getBubbleLeft( + mBubbleData.getBubbles().indexOf(mExpandedBubble)); - mExpandedViewContainer.setTranslationX(getCollapsedX()); - mExpandedViewContainer.setTranslationY(getCollapsedY()); - mExpandedViewContainer.setAlpha(0f); + mExpandedViewContainer.setAlpha(1f); + mExpandedViewContainer.setVisibility(View.VISIBLE); - mExpandedViewXAnim.animateToFinalPosition(0f); - mExpandedViewYAnim.animateToFinalPosition(getExpandedViewY()); - mExpandedViewContainer.animate() - .setDuration(100) - .alpha(1f); - } + mExpandedViewContainerMatrix.setScale( + 0f, 0f, expandingFromBubbleDestinationX + mBubbleSize / 2f, getExpandedViewY()); + mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); - private float getCollapsedX() { - return mStackAnimationController.getStackPosition().x < getWidth() / 2 - ? -mExpandedAnimateXDistance - : mExpandedAnimateXDistance; - } + mExpandedViewContainer.postDelayed(() -> { + if (!mIsExpanded) { + return; + } - private float getCollapsedY() { - return Math.min(mStackAnimationController.getStackPosition().y, - mExpandedAnimateYDistance); + PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) + .spring(AnimatableScaleMatrix.SCALE_X, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .spring(AnimatableScaleMatrix.SCALE_Y, + AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), + mScaleInSpringConfig) + .addUpdateListener((target, values) -> { + mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix); + }) + .withEndActions(() -> { + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); + } + }) + .start(); + }, 25); } private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) { @@ -1802,7 +1961,7 @@ public class BubbleStackView extends FrameLayout public void subtractObscuredTouchableRegion(Region touchableRegion, View view) { // If the notification shade is expanded, or the manage menu is open, we shouldn't let the // ActivityView steal any touch events from any location. - if (mNotificationShadeWindowController.getPanelExpanded() || mShowingManage) { + if (!mIsExpanded || mShowingManage) { touchableRegion.setEmpty(); } } @@ -2248,11 +2407,114 @@ public class BubbleStackView extends FrameLayout if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { BubbleExpandedView bev = mExpandedBubble.getExpandedView(); + bev.setContentVisibility(false); + mExpandedViewContainerMatrix.setScaleX(0f); + mExpandedViewContainerMatrix.setScaleY(0f); + mExpandedViewContainer.setVisibility(View.INVISIBLE); + mExpandedViewContainer.setAlpha(0f); mExpandedViewContainer.addView(bev); bev.setManageClickListener((view) -> showManageMenu(!mShowingManage)); bev.populateExpandedView(); - mExpandedViewContainer.setVisibility(VISIBLE); - mExpandedViewContainer.setAlpha(1.0f); + + if (!mIsExpansionAnimating) { + mSurfaceSynchronizer.syncSurfaceAndRun(() -> { + post(this::animateSwitchBubbles); + }); + } + } + } + + /** + * Requests a snapshot from the currently expanded bubble's ActivityView and displays it in a + * SurfaceView. This allows us to load a newly expanded bubble's Activity into the ActivityView, + * while animating the (screenshot of the) previously selected bubble's content away. + * + * @param onComplete Callback to run once we're done here - called with 'false' if something + * went wrong, or 'true' if the SurfaceView is now showing a screenshot of the + * expanded bubble. + */ + private void screenshotAnimatingOutBubbleIntoSurface(Consumer<Boolean> onComplete) { + if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { + // You can't animate null. + onComplete.accept(false); + return; + } + + final BubbleExpandedView animatingOutExpandedView = mExpandedBubble.getExpandedView(); + + // Release the previous screenshot if it hasn't been released already. + if (mAnimatingOutBubbleBuffer != null) { + releaseAnimatingOutBubbleBuffer(); + } + + try { + mAnimatingOutBubbleBuffer = animatingOutExpandedView.snapshotActivitySurface(); + } catch (Exception e) { + // If we fail for any reason, print the stack trace and then notify the callback of our + // failure. This is not expected to occur, but it's not worth crashing over. + Log.wtf(TAG, e); + onComplete.accept(false); + } + + if (mAnimatingOutBubbleBuffer == null + || mAnimatingOutBubbleBuffer.getGraphicBuffer() == null) { + // While no exception was thrown, we were unable to get a snapshot. + onComplete.accept(false); + return; + } + + // Make sure the surface container's properties have been reset. + PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel(); + mAnimatingOutSurfaceContainer.setScaleX(1f); + mAnimatingOutSurfaceContainer.setScaleY(1f); + mAnimatingOutSurfaceContainer.setTranslationX(0); + mAnimatingOutSurfaceContainer.setTranslationY(0); + + final int[] activityViewLocation = + mExpandedBubble.getExpandedView().getActivityViewLocationOnScreen(); + final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen(); + + // Translate the surface to overlap the real ActivityView. + mAnimatingOutSurfaceContainer.setTranslationY( + activityViewLocation[1] - surfaceViewLocation[1]); + + // Set the width/height of the SurfaceView to match the snapshot. + mAnimatingOutSurfaceView.getLayoutParams().width = + mAnimatingOutBubbleBuffer.getGraphicBuffer().getWidth(); + mAnimatingOutSurfaceView.getLayoutParams().height = + mAnimatingOutBubbleBuffer.getGraphicBuffer().getHeight(); + mAnimatingOutSurfaceView.requestLayout(); + + // Post to wait for layout. + post(() -> { + // The buffer might have been destroyed if the user is mashing on bubbles, that's okay. + if (mAnimatingOutBubbleBuffer.getGraphicBuffer().isDestroyed()) { + onComplete.accept(false); + return; + } + + if (!mIsExpanded) { + onComplete.accept(false); + return; + } + + // Attach the buffer! We're now displaying the snapshot. + mAnimatingOutSurfaceView.getHolder().getSurface().attachAndQueueBufferWithColorSpace( + mAnimatingOutBubbleBuffer.getGraphicBuffer(), + mAnimatingOutBubbleBuffer.getColorSpace()); + + mSurfaceSynchronizer.syncSurfaceAndRun(() -> post(() -> onComplete.accept(true))); + }); + } + + /** + * Releases the buffer containing the screenshot of the animating-out bubble, if it exists and + * isn't yet destroyed. + */ + private void releaseAnimatingOutBubbleBuffer() { + if (mAnimatingOutBubbleBuffer != null + && !mAnimatingOutBubbleBuffer.getGraphicBuffer().isDestroyed()) { + mAnimatingOutBubbleBuffer.getGraphicBuffer().destroy(); } } @@ -2262,19 +2524,10 @@ public class BubbleStackView extends FrameLayout } mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE); - if (mIsExpanded) { - final float y = getExpandedViewY(); - if (!mExpandedViewYAnim.isRunning()) { - // We're not animating so set the value - mExpandedViewContainer.setTranslationY(y); - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().updateView(); - } - } else { - // We are animating so update the value; there is an end listener on the animator - // that will ensure expandedeView.updateView gets called. - mExpandedViewYAnim.animateToFinalPosition(y); - } + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + mExpandedViewContainer.setTranslationY(getExpandedViewY()); + mExpandedBubble.getExpandedView().updateView( + mExpandedViewContainer.getLocationOnScreen()); } mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java new file mode 100644 index 000000000000..ae7833634794 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java @@ -0,0 +1,137 @@ +/* + * 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.bubbles.animation; + +import android.graphics.Matrix; + +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.FloatPropertyCompat; + +/** + * Matrix whose scale properties can be animated using physics animations, via the {@link #SCALE_X} + * and {@link #SCALE_Y} FloatProperties. + * + * This is useful when you need to perform a scale animation with a pivot point, since pivot points + * are not supported by standard View scale operations but are supported by matrices. + * + * NOTE: DynamicAnimation assumes that all custom properties are denominated in pixels, and thus + * considers 1 to be the smallest user-visible change for custom properties. This means that if you + * animate {@link #SCALE_X} and {@link #SCALE_Y} to 3f, for example, the animation would have only + * three frames. + * + * To work around this, whenever animating to a desired scale value, animate to the value returned + * by {@link #getAnimatableValueForScaleFactor} instead. The SCALE_X and SCALE_Y properties will + * convert that (larger) value into the appropriate scale factor when scaling the matrix. + */ +public class AnimatableScaleMatrix extends Matrix { + + /** + * The X value of the scale. + * + * NOTE: This must be set or animated to the value returned by + * {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself. + */ + public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_X = + new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleX") { + @Override + public float getValue(AnimatableScaleMatrix object) { + return getAnimatableValueForScaleFactor(object.mScaleX); + } + + @Override + public void setValue(AnimatableScaleMatrix object, float value) { + object.setScaleX(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE); + } + }; + + /** + * The Y value of the scale. + * + * NOTE: This must be set or animated to the value returned by + * {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself. + */ + public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_Y = + new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleY") { + @Override + public float getValue(AnimatableScaleMatrix object) { + return getAnimatableValueForScaleFactor(object.mScaleY); + } + + @Override + public void setValue(AnimatableScaleMatrix object, float value) { + object.setScaleY(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE); + } + }; + + private float mScaleX = 1f; + private float mScaleY = 1f; + + private float mPivotX = 0f; + private float mPivotY = 0f; + + /** + * Return the value to animate SCALE_X or SCALE_Y to in order to achieve the desired scale + * factor. + */ + public static float getAnimatableValueForScaleFactor(float scale) { + return scale * (1f / DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE); + } + + @Override + public void setScale(float sx, float sy, float px, float py) { + mScaleX = sx; + mScaleY = sy; + mPivotX = px; + mPivotY = py; + super.setScale(mScaleX, mScaleY, mPivotX, mPivotY); + } + + public void setScaleX(float scaleX) { + mScaleX = scaleX; + super.setScale(mScaleX, mScaleY, mPivotX, mPivotY); + } + + public void setScaleY(float scaleY) { + mScaleY = scaleY; + super.setScale(mScaleX, mScaleY, mPivotX, mPivotY); + } + + public void setPivotX(float pivotX) { + mPivotX = pivotX; + super.setScale(mScaleX, mScaleY, mPivotX, mPivotY); + } + + public void setPivotY(float pivotY) { + mPivotY = pivotY; + super.setScale(mScaleX, mScaleY, mPivotX, mPivotY); + } + + public float getScaleX() { + return mScaleX; + } + + public float getScaleY() { + return mScaleY; + } + + public float getPivotX() { + return mPivotX; + } + + public float getPivotY() { + return mPivotY; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 76ff1afef3f7..86fe10dddc2c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -56,7 +56,7 @@ public class ExpandedAnimationController private static final int ANIMATE_TRANSLATION_FACTOR = 4; /** Duration of the expand/collapse target path animation. */ - private static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175; + public static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175; /** Stiffness for the expand/collapse path-following animation. */ private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java index fd1f879e7f4b..3ef20444cb52 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java @@ -1034,13 +1034,13 @@ public class StackAnimationController extends mMagnetizedStack.getFlingToTargetMinVelocity() /* default */); final float maxVelocity = Settings.Secure.getFloat(contentResolver, "bubble_dismiss_stick_max_velocity", - mMagnetizedStack.getStickToTargetMaxVelocity() /* default */); + mMagnetizedStack.getStickToTargetMaxXVelocity() /* default */); final float targetWidth = Settings.Secure.getFloat(contentResolver, "bubble_dismiss_target_width_percent", mMagnetizedStack.getFlingToTargetWidthPercent() /* default */); mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity); - mMagnetizedStack.setStickToTargetMaxVelocity(maxVelocity); + mMagnetizedStack.setStickToTargetMaxXVelocity(maxVelocity); mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth); return mMagnetizedStack; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java index d1d07f6ba4ce..097932e5f447 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java @@ -20,6 +20,7 @@ import android.app.INotificationManager; import android.content.Context; import android.view.WindowManager; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.BubbleData; import com.android.systemui.bubbles.BubbleDataRepository; @@ -70,6 +71,7 @@ public interface BubbleModule { BubbleDataRepository bubbleDataRepository, SysUiState sysUiState, INotificationManager notifManager, + IStatusBarService statusBarService, WindowManager windowManager) { return new BubbleController( context, @@ -91,6 +93,7 @@ public interface BubbleModule { bubbleDataRepository, sysUiState, notifManager, + statusBarService, windowManager); } } 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 07319207d9ab..45ba1e6012fe 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -190,11 +190,6 @@ interface ControlsController : UserAwareController { fun countFavoritesForComponent(componentName: ComponentName): Int /** - * TEMPORARY for testing - */ - fun resetFavorites() - - /** * Interface for structure to pass data to [ControlsFavoritingActivity]. */ interface LoadData { 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 93f0c7f41ce3..ebdcdccead90 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -524,13 +524,6 @@ class ControlsControllerImpl @Inject constructor ( } } - override fun resetFavorites() { - executor.execute { - Favorites.clear() - persistenceWrapper.storeFavorites(Favorites.getAllStructures()) - } - } - override fun refreshStatus(componentName: ComponentName, control: Control) { if (!confirmAvailability()) { Log.d(TAG, "Controls not available") diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index d31b6eb9d857..0f5aef7eeec1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -19,16 +19,13 @@ package com.android.systemui.controls.ui import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ObjectAnimator -import android.app.AlertDialog import android.content.ComponentName import android.content.Context -import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences import android.content.res.Configuration import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable -import android.os.Process import android.service.controls.Control import android.util.Log import android.util.TypedValue @@ -36,7 +33,6 @@ import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowManager import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator import android.widget.AdapterView @@ -272,8 +268,7 @@ class ControlsUiControllerImpl @Inject constructor ( private fun createMenu() { val items = arrayOf( context.resources.getString(R.string.controls_menu_add), - context.resources.getString(R.string.controls_menu_edit), - "Reset" + context.resources.getString(R.string.controls_menu_edit) ) var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items) @@ -298,10 +293,6 @@ class ControlsUiControllerImpl @Inject constructor ( 0 -> startFavoritingActivity(view.context, selectedStructure) // 1: Edit controls 1 -> startEditingActivity(view.context, selectedStructure) - // 2: TEMPORARY for reset controls - 2 -> showResetConfirmation() - else -> Log.w(ControlsUiController.TAG, - "Unsupported index ($pos) on 'more' menu selection") } dismiss() } @@ -312,39 +303,6 @@ class ControlsUiControllerImpl @Inject constructor ( }) } - private fun showResetConfirmation() { - val builder = AlertDialog.Builder( - context, - android.R.style.Theme_DeviceDefault_Dialog_Alert - ).apply { - setMessage("For testing purposes: Would you like to " + - "reset your favorited device controls?") - setPositiveButton( - android.R.string.ok, - DialogInterface.OnClickListener { dialog, _ -> - val userHandle = Process.myUserHandle() - val userContext = context.createContextAsUser(userHandle, 0) - val prefs = userContext.getSharedPreferences( - "controls_prefs", Context.MODE_PRIVATE) - prefs.edit().remove("SeedingCompleted").apply() - controlsController.get().resetFavorites() - dialog.dismiss() - context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) - }) - setNegativeButton( - android.R.string.cancel, - DialogInterface.OnClickListener { - dialog, _ -> dialog.cancel() - } - ) - } - builder.create().apply { - getWindow().apply { - setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY) - } - }.show() - } - private fun createDropDown(items: List<SelectionItem>) { items.forEach { RenderInfo.registerComponentIcon(it.componentName, it.icon) diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java index 6b71f1e9d86f..1dbbb4d69493 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPopupMenu.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; +import android.util.LayoutDirection; import android.view.View; import android.view.View.MeasureSpec; import android.view.WindowManager; @@ -106,7 +107,11 @@ public class GlobalActionsPopupMenu extends ListPopupWindow { listView.setPadding(0, mMenuVerticalPadding, 0, mMenuVerticalPadding); setWidth(width); - setHorizontalOffset(getAnchorView().getWidth() - mGlobalActionsSidePadding - width); + if (getAnchorView().getLayoutDirection() == LayoutDirection.LTR) { + setHorizontalOffset(getAnchorView().getWidth() - mGlobalActionsSidePadding - width); + } else { + setHorizontalOffset(mGlobalActionsSidePadding); + } } super.show(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java index 03b1ddca4648..7f7e1085d497 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java @@ -17,7 +17,6 @@ package com.android.systemui.pip; import android.animation.Animator; -import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.content.Context; @@ -27,7 +26,6 @@ import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Interpolators; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -76,12 +74,15 @@ public class PipAnimationController { || direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN; } + private final Interpolator mFastOutSlowInInterpolator; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private PipTransitionAnimator mCurrentAnimator; @Inject PipAnimationController(Context context, PipSurfaceTransactionHelper helper) { + mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_slow_in); mSurfaceTransactionHelper = helper; } @@ -103,11 +104,10 @@ public class PipAnimationController { } @SuppressWarnings("unchecked") - PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds, - Rect sourceHintRect) { + PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds) { if (mCurrentAnimator == null) { mCurrentAnimator = setupPipTransitionAnimator( - PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect)); + PipTransitionAnimator.ofBounds(leash, startBounds, endBounds)); } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA && mCurrentAnimator.isRunning()) { // If we are still animating the fade into pip, then just move the surface and ensure @@ -122,7 +122,7 @@ public class PipAnimationController { } else { mCurrentAnimator.cancel(); mCurrentAnimator = setupPipTransitionAnimator( - PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect)); + PipTransitionAnimator.ofBounds(leash, startBounds, endBounds)); } return mCurrentAnimator; } @@ -133,7 +133,7 @@ public class PipAnimationController { private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) { animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); - animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + animator.setInterpolator(mFastOutSlowInInterpolator); animator.setFloatValues(FRACTION_START, FRACTION_END); return animator; } @@ -331,7 +331,6 @@ public class PipAnimationController { @Override void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { getSurfaceTransactionHelper() - .resetScale(tx, leash, getDestinationBounds()) .crop(tx, leash, getDestinationBounds()) .round(tx, leash, shouldApplyCornerRadius()); tx.show(leash); @@ -347,46 +346,35 @@ public class PipAnimationController { } static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash, - Rect startValue, Rect endValue, Rect sourceHintRect) { - // Just for simplicity we'll interpolate between the source rect hint insets and empty - // insets to calculate the window crop - final Rect initialStartValue = new Rect(startValue); - final Rect sourceHintRectInsets = sourceHintRect != null - ? new Rect(sourceHintRect.left - startValue.left, - sourceHintRect.top - startValue.top, - startValue.right - sourceHintRect.right, - startValue.bottom - sourceHintRect.bottom) - : null; - final Rect sourceInsets = new Rect(0, 0, 0, 0); - + Rect startValue, Rect endValue) { // construct new Rect instances in case they are recycled return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS, endValue, new Rect(startValue), new Rect(endValue)) { - private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); - private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect()); + private final Rect mTmpRect = new Rect(); + + private int getCastedFractionValue(float start, float end, float fraction) { + return (int) (start * (1 - fraction) + end * fraction + .5f); + } @Override void applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction) { final Rect start = getStartValue(); final Rect end = getEndValue(); - Rect bounds = mRectEvaluator.evaluate(fraction, start, end); - setCurrentValue(bounds); + mTmpRect.set( + getCastedFractionValue(start.left, end.left, fraction), + getCastedFractionValue(start.top, end.top, fraction), + getCastedFractionValue(start.right, end.right, fraction), + getCastedFractionValue(start.bottom, end.bottom, fraction)); + setCurrentValue(mTmpRect); if (inScaleTransition()) { if (isOutPipDirection(getTransitionDirection())) { - getSurfaceTransactionHelper().scale(tx, leash, end, bounds); + getSurfaceTransactionHelper().scale(tx, leash, end, mTmpRect); } else { - getSurfaceTransactionHelper().scale(tx, leash, start, bounds); + getSurfaceTransactionHelper().scale(tx, leash, start, mTmpRect); } } else { - if (sourceHintRectInsets != null) { - Rect insets = mInsetsEvaluator.evaluate(fraction, sourceInsets, - sourceHintRectInsets); - getSurfaceTransactionHelper().scaleAndCrop(tx, leash, initialStartValue, - bounds, insets); - } else { - getSurfaceTransactionHelper().scale(tx, leash, start, bounds); - } + getSurfaceTransactionHelper().crop(tx, leash, mTmpRect); } tx.apply(); } @@ -402,11 +390,11 @@ public class PipAnimationController { @Override void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { + if (!inScaleTransition()) return; // NOTE: intentionally does not apply the transaction here. // this end transaction should get executed synchronously with the final // WindowContainerTransaction in task organizer - getSurfaceTransactionHelper() - .resetScale(tx, leash, getDestinationBounds()) + getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds()) .crop(tx, leash, getDestinationBounds()); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 26576940740f..0d3a16ec1028 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -296,14 +296,6 @@ public class PipBoundsHandler { */ public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, Rect outInsetBounds, int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) { - // Calculate the snap fraction of the current stack along the old movement bounds - final Rect postChangeStackBounds = new Rect(oldBounds); - final float snapFraction = getSnapFraction(postChangeStackBounds); - - // Update the display layout, note that we have to do this on every rotation even if we - // aren't in PIP since we need to update the display layout to get the right resources - mDisplayLayout.rotateTo(mContext.getResources(), toRotation); - // Bail early if the event is not sent to current {@link #mDisplayInfo} if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) { return false; @@ -320,6 +312,13 @@ public class PipBoundsHandler { return false; } + // Calculate the snap fraction of the current stack along the old movement bounds + final Rect postChangeStackBounds = new Rect(oldBounds); + final float snapFraction = getSnapFraction(postChangeStackBounds); + + // Update the display layout + mDisplayLayout.rotateTo(mContext.getResources(), toRotation); + // Populate the new {@link #mDisplayInfo}. // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation, // therefore, the width/height may require a swap first. diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java index 65ea887259be..fc41d2ea8862 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java @@ -44,7 +44,6 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf private final float[] mTmpFloat9 = new float[9]; private final RectF mTmpSourceRectF = new RectF(); private final RectF mTmpDestinationRectF = new RectF(); - private final Rect mTmpDestinationRect = new Rect(); @Inject public PipSurfaceTransactionHelper(Context context, ConfigurationController configController) { @@ -91,30 +90,7 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf mTmpDestinationRectF.set(destinationBounds); mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); tx.setMatrix(leash, mTmpTransform, mTmpFloat9) - .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top); - return this; - } - - /** - * Operates the scale (setMatrix) on a given transaction and leash - * @return same {@link PipSurfaceTransactionHelper} instance for method chaining - */ - PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash, - Rect sourceBounds, Rect destinationBounds, Rect insets) { - mTmpSourceRectF.set(sourceBounds); - mTmpDestinationRect.set(sourceBounds); - mTmpDestinationRect.inset(insets); - // Scale by the shortest edge and offset such that the top/left of the scaled inset source - // rect aligns with the top/left of the destination bounds - final float scale = sourceBounds.width() <= sourceBounds.height() - ? (float) destinationBounds.width() / sourceBounds.width() - : (float) destinationBounds.height() / sourceBounds.height(); - final float left = destinationBounds.left - insets.left * scale; - final float top = destinationBounds.top - insets.top * scale; - mTmpTransform.setScale(scale, scale); - tx.setMatrix(leash, mTmpTransform, mTmpFloat9) - .setWindowCrop(leash, mTmpDestinationRect) - .setPosition(leash, left, top); + .setPosition(leash, destinationBounds.left, destinationBounds.top); return this; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 42e0c56d6cc8..b93e07e65c73 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -143,10 +143,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements case MSG_RESIZE_ANIMATE: { Rect currentBounds = (Rect) args.arg2; Rect toBounds = (Rect) args.arg3; - Rect sourceHintRect = (Rect) args.arg4; int duration = args.argi2; - animateResizePip(currentBounds, toBounds, sourceHintRect, - args.argi1 /* direction */, duration); + animateResizePip(currentBounds, toBounds, args.argi1 /* direction */, duration); if (updateBoundsCallback != null) { updateBoundsCallback.accept(toBounds); } @@ -296,8 +294,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements public void onTransactionReady(int id, SurfaceControl.Transaction t) { t.apply(); scheduleAnimateResizePip(mLastReportedBounds, destinationBounds, - null /* sourceHintRect */, direction, animationDurationMs, - null /* updateBoundsCallback */); + direction, animationDurationMs, null /* updateBoundsCallback */); mInPip = false; } }); @@ -360,8 +357,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { - final Rect sourceHintRect = getValidSourceHintRect(info, currentBounds); - scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect, + scheduleAnimateResizePip(currentBounds, destinationBounds, TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration, null /* updateBoundsCallback */); } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { @@ -372,21 +368,6 @@ public class PipTaskOrganizer extends TaskOrganizer implements } } - /** - * Returns the source hint rect if it is valid (if provided and is contained by the current - * task bounds). - */ - private Rect getValidSourceHintRect(ActivityManager.RunningTaskInfo info, Rect sourceBounds) { - final Rect sourceHintRect = info.pictureInPictureParams != null - && info.pictureInPictureParams.hasSourceBoundsHint() - ? info.pictureInPictureParams.getSourceRectHint() - : null; - if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) { - return sourceHintRect; - } - return null; - } - private void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) { // If we are fading the PIP in, then we should move the pip to the final location as // soon as possible, but set the alpha immediately since the transaction can take a @@ -523,8 +504,18 @@ public class PipTaskOrganizer extends TaskOrganizer implements // this could happen if rotation finishes before the animation mLastReportedBounds.set(destinationBoundsOut); scheduleFinishResizePip(mLastReportedBounds); - } else if (!mLastReportedBounds.isEmpty()) { - destinationBoundsOut.set(mLastReportedBounds); + } else { + // There could be an animation on-going. If there is one on-going, last-reported + // bounds isn't yet updated. We'll use the animator's bounds instead. + if (animator != null && animator.isRunning()) { + if (!animator.getDestinationBounds().isEmpty()) { + destinationBoundsOut.set(animator.getDestinationBounds()); + } + } else { + if (!mLastReportedBounds.isEmpty()) { + destinationBoundsOut.set(mLastReportedBounds); + } + } } return; } @@ -571,13 +562,13 @@ public class PipTaskOrganizer extends TaskOrganizer implements Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred"); return; } - scheduleAnimateResizePip(mLastReportedBounds, toBounds, null /* sourceHintRect */, + scheduleAnimateResizePip(mLastReportedBounds, toBounds, TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback); } private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds, - Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, - int durationMs, Consumer<Rect> updateBoundsCallback) { + @PipAnimationController.TransitionDirection int direction, int durationMs, + Consumer<Rect> updateBoundsCallback) { if (!mInPip) { // can be initiated in other component, ignore if we are no longer in PIP return; @@ -587,7 +578,6 @@ public class PipTaskOrganizer extends TaskOrganizer implements args.arg1 = updateBoundsCallback; args.arg2 = currentBounds; args.arg3 = destinationBounds; - args.arg4 = sourceHintRect; args.argi1 = direction; args.argi2 = durationMs; mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args)); @@ -687,8 +677,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements } final Rect destinationBounds = new Rect(originalBounds); destinationBounds.offset(xOffset, yOffset); - animateResizePip(originalBounds, destinationBounds, null /* sourceHintRect */, - TRANSITION_DIRECTION_SAME, durationMs); + animateResizePip(originalBounds, destinationBounds, TRANSITION_DIRECTION_SAME, durationMs); } private void resizePip(Rect destinationBounds) { @@ -766,7 +755,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements WindowOrganizer.applyTransaction(wct); } - private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect, + private void animateResizePip(Rect currentBounds, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, int durationMs) { if (Looper.myLooper() != mUpdateHandler.getLooper()) { throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of " @@ -778,7 +767,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements return; } mPipAnimationController - .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect) + .getAnimator(mLeash, currentBounds, destinationBounds) .setTransitionDirection(direction) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(durationMs) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index fc6c2be1ce9a..1972b869ba25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -19,13 +19,16 @@ package com.android.systemui.statusbar.notification import android.app.Notification import android.content.Context import android.content.pm.LauncherApps +import android.os.Handler import android.service.notification.NotificationListenerService.Ranking import android.service.notification.NotificationListenerService.RankingMap import com.android.internal.statusbar.NotificationVisibility import com.android.internal.widget.ConversationLayout +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationContentView +import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.phone.NotificationGroupManager import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject @@ -62,7 +65,8 @@ class ConversationNotificationProcessor @Inject constructor( class ConversationNotificationManager @Inject constructor( private val notificationEntryManager: NotificationEntryManager, private val notificationGroupManager: NotificationGroupManager, - private val context: Context + private val context: Context, + @Main private val mainHandler: Handler ) { // Need this state to be thread safe, since it's accessed from the ui thread // (NotificationEntryListener) and a bg thread (NotificationContentInflater) @@ -72,32 +76,41 @@ class ConversationNotificationManager @Inject constructor( init { notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener { - override fun onNotificationRankingUpdated(rankingMap: RankingMap) { fun getLayouts(view: NotificationContentView) = sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild) val ranking = Ranking() - states.keys.asSequence() + val activeConversationEntries = states.keys.asSequence() .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) } - .forEach { entry -> - if (rankingMap.getRanking(entry.sbn.key, ranking) && - ranking.isConversation) { - val important = ranking.channel.isImportantConversation - var changed = false - entry.row?.layouts?.asSequence() - ?.flatMap(::getLayouts) - ?.mapNotNull { it as? ConversationLayout } - ?.forEach { - if (important != it.isImportantConversation) { - it.setIsImportantConversation(important) - changed = true - } - } - if (changed) { - notificationGroupManager.updateIsolation(entry) - } + for (entry in activeConversationEntries) { + if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { + val important = ranking.channel.isImportantConversation + val layouts = entry.row?.layouts?.asSequence() + ?.flatMap(::getLayouts) + ?.mapNotNull { it as? ConversationLayout } + ?: emptySequence() + var changed = false + for (layout in layouts) { + if (important == layout.isImportantConversation) { + continue + } + changed = true + if (important && entry.isMarkedForUserTriggeredMovement) { + // delay this so that it doesn't animate in until after + // the notif has been moved in the shade + mainHandler.postDelayed({ + layout.setIsImportantConversation( + important, true /* animate */) + }, IMPORTANCE_ANIMATION_DELAY.toLong()) + } else { + layout.setIsImportantConversation(important) } } + if (changed) { + notificationGroupManager.updateIsolation(entry) + } + } + } } override fun onEntryInflated(entry: NotificationEntry) { @@ -177,9 +190,16 @@ class ConversationNotificationManager @Inject constructor( private fun resetBadgeUi(row: ExpandableNotificationRow): Unit = (row.layouts?.asSequence() ?: emptySequence()) - .flatMap { layout -> layout.allViews.asSequence()} + .flatMap { layout -> layout.allViews.asSequence() } .mapNotNull { view -> view as? ConversationLayout } .forEach { convoLayout -> convoLayout.setUnreadCount(0) } private data class ConversationState(val unreadCount: Int, val notification: Notification) + + companion object { + private const val IMPORTANCE_ANIMATION_DELAY = + StackStateAnimator.ANIMATION_DURATION_STANDARD + + StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE + + 100 + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java index 87612f15ed3d..e445c9d73bbb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AppOpsInfo.java @@ -188,7 +188,9 @@ public class AppOpsInfo extends LinearLayout implements NotificationGuts.GutsCon @Override public boolean handleCloseControls(boolean save, boolean force) { - mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false); + if (mMetricsLogger != null) { + mMetricsLogger.visibility(MetricsEvent.APP_OPS_GUTS, false); + } return false; } 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 e9d89589172e..bee2f7002823 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 @@ -44,6 +44,7 @@ import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.os.Handler; import android.os.RemoteException; +import android.os.UserHandle; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.text.TextUtils; @@ -53,7 +54,6 @@ import android.transition.TransitionManager; import android.transition.TransitionSet; import android.util.AttributeSet; import android.util.Log; -import android.util.Slog; import android.view.LayoutInflater; import android.view.View; import android.view.accessibility.AccessibilityEvent; @@ -508,10 +508,10 @@ public class NotificationConversationInfo extends LinearLayout implements mBgHandler.post( new UpdateChannelRunnable(mINotificationManager, mPackageName, mAppUid, mSelectedAction, mNotificationChannel)); - mMainHandler.postDelayed(() -> { - mEntry.markForUserTriggeredMovement(true); - mVisualStabilityManager.temporarilyAllowReordering(); - }, StackStateAnimator.ANIMATION_DURATION_STANDARD); + mEntry.markForUserTriggeredMovement(true); + mMainHandler.postDelayed( + mVisualStabilityManager::temporarilyAllowReordering, + StackStateAnimator.ANIMATION_DURATION_STANDARD); } private boolean shouldShowPriorityOnboarding() { @@ -541,7 +541,9 @@ public class NotificationConversationInfo extends LinearLayout implements .setView(onboardingView) .setIgnoresDnd(ignoreDnd) .setShowsAsBubble(showAsBubble) - .setIcon(((ImageView) findViewById(R.id.conversation_icon)).getDrawable()) + .setIcon(mIconFactory.getBaseIconDrawable(mShortcutInfo)) + .setBadge(mIconFactory.getAppBadge( + mPackageName, UserHandle.getUserId(mSbn.getUid()))) .setOnSettingsClick(mOnConversationSettingsClickListener) .build(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java index f1fe54ad4024..186ffa67f046 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java @@ -148,8 +148,7 @@ public class PartialConversationInfo extends LinearLayout implements } private void bindHeader() { - bindConversationDetails(); - + bindPackage(); // Delegate bindDelegate(); } @@ -180,51 +179,6 @@ public class PartialConversationInfo extends LinearLayout implements }); } - private void bindConversationDetails() { - final TextView channelName = findViewById(R.id.parent_channel_name); - channelName.setText(mNotificationChannel.getName()); - - bindGroup(); - bindName(); - bindPackage(); - bindIcon(); - } - - private void bindName() { - TextView name = findViewById(R.id.name); - Bundle extras = mSbn.getNotification().extras; - CharSequence nameString = extras.getCharSequence(Notification.EXTRA_CONVERSATION_TITLE, ""); - if (TextUtils.isEmpty(nameString)) { - nameString = extras.getCharSequence(Notification.EXTRA_TITLE, ""); - } - name.setText(nameString); - } - - private void bindIcon() { - ImageView image = findViewById(R.id.conversation_icon); - if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) { - // TODO: maybe use a generic group icon, or a composite of recent senders - image.setImageDrawable(mPkgIcon); - } else { - final List<Notification.MessagingStyle.Message> messages = - Notification.MessagingStyle.Message.getMessagesFromBundleArray( - (Parcelable[]) mSbn.getNotification().extras.get( - Notification.EXTRA_MESSAGES)); - - final Notification.MessagingStyle.Message latestMessage = - Notification.MessagingStyle.findLatestIncomingMessage(messages); - Icon personIcon = null; - if (latestMessage != null && latestMessage.getSenderPerson() != null) { - personIcon = latestMessage.getSenderPerson().getIcon(); - } - if (personIcon != null) { - image.setImageIcon(latestMessage.getSenderPerson().getIcon()); - } else { - image.setImageDrawable(mPkgIcon); - } - } - } - private void bindPackage() { ApplicationInfo info; try { @@ -241,6 +195,10 @@ public class PartialConversationInfo extends LinearLayout implements } catch (PackageManager.NameNotFoundException e) { mPkgIcon = mPm.getDefaultActivityIcon(); } + TextView name = findViewById(R.id.name); + name.setText(mAppName); + ImageView image = findViewById(R.id.icon); + image.setImageDrawable(mPkgIcon); } private void bindDelegate() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt index c88f0bdc2acb..fab367df8fde 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt @@ -16,41 +16,57 @@ package com.android.systemui.statusbar.notification.row +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.animation.ValueAnimator import android.app.Dialog import android.content.Context import android.graphics.Color import android.graphics.PixelFormat import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable import android.text.SpannableStringBuilder import android.text.style.BulletSpan import android.view.Gravity import android.view.View +import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.Window import android.view.WindowInsets.Type.statusBars import android.view.WindowManager +import android.view.animation.Interpolator +import android.view.animation.PathInterpolator import android.widget.ImageView import android.widget.TextView +import com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN import com.android.systemui.Prefs import com.android.systemui.R import com.android.systemui.statusbar.notification.row.NotificationConversationInfo.OnConversationSettingsClickListener import javax.inject.Inject + /** * Controller to handle presenting the priority conversations onboarding dialog */ class PriorityOnboardingDialogController @Inject constructor( - val view: View, - val context: Context, - val ignoresDnd: Boolean, - val showsAsBubble: Boolean, - val icon : Drawable, - val onConversationSettingsClickListener : OnConversationSettingsClickListener + val view: View, + val context: Context, + private val ignoresDnd: Boolean, + private val showsAsBubble: Boolean, + val icon : Drawable, + private val onConversationSettingsClickListener : OnConversationSettingsClickListener, + val badge : Drawable ) { private lateinit var dialog: Dialog + private val OVERSHOOT: Interpolator = PathInterpolator(0.4f, 0f, 0.2f, 1.4f) + private val IMPORTANCE_ANIM_DELAY = 150L + private val IMPORTANCE_ANIM_GROW_DURATION = 250L + private val IMPORTANCE_ANIM_SHRINK_DURATION = 200L + private val IMPORTANCE_ANIM_SHRINK_DELAY = 25L fun init() { initDialog() @@ -81,6 +97,7 @@ class PriorityOnboardingDialogController @Inject constructor( private lateinit var icon: Drawable private lateinit var onConversationSettingsClickListener : OnConversationSettingsClickListener + private lateinit var badge : Drawable fun setView(v: View): Builder { view = v @@ -106,6 +123,10 @@ class PriorityOnboardingDialogController @Inject constructor( icon = draw return this } + fun setBadge(badge : Drawable) : Builder { + this.badge = badge + return this + } fun setOnSettingsClick(onClick : OnConversationSettingsClickListener) : Builder { onConversationSettingsClickListener = onClick @@ -115,7 +136,7 @@ class PriorityOnboardingDialogController @Inject constructor( fun build(): PriorityOnboardingDialogController { val controller = PriorityOnboardingDialogController( view, context, ignoresDnd, showAsBubble, icon, - onConversationSettingsClickListener) + onConversationSettingsClickListener, badge) return controller } } @@ -143,6 +164,65 @@ class PriorityOnboardingDialogController @Inject constructor( } findViewById<ImageView>(R.id.conversation_icon)?.setImageDrawable(icon) + findViewById<ImageView>(R.id.icon)?.setImageDrawable(badge) + val mImportanceRingView = findViewById<ImageView>(R.id.conversation_icon_badge_ring) + val conversationIconBadgeBg = findViewById<ImageView>(R.id.conversation_icon_badge_bg) + + val ring: GradientDrawable = mImportanceRingView.drawable as GradientDrawable + ring.mutate() + val bg = conversationIconBadgeBg.drawable as GradientDrawable + bg.mutate() + val ringColor = context.getResources() + .getColor(com.android.internal.R.color.conversation_important_highlight) + val standardThickness = context.resources.getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_stroke_width) + val largeThickness = context.resources.getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_anim_max_stroke_width) + val standardSize = context.resources.getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_size) + val baseSize = standardSize - standardThickness * 2 + val largeSize = baseSize + largeThickness * 2 + val bgSize = context.resources.getDimensionPixelSize( + com.android.internal.R.dimen.conversation_icon_size_badged) + + val animatorUpdateListener: ValueAnimator.AnimatorUpdateListener + = ValueAnimator.AnimatorUpdateListener { animation -> + val strokeWidth = animation.animatedValue as Int + ring.setStroke(strokeWidth, ringColor) + val newSize = baseSize + strokeWidth * 2 + ring.setSize(newSize, newSize) + mImportanceRingView.invalidate() + } + + val growAnimation: ValueAnimator = ValueAnimator.ofInt(0, largeThickness) + growAnimation.interpolator = LINEAR_OUT_SLOW_IN + growAnimation.duration = IMPORTANCE_ANIM_GROW_DURATION + growAnimation.addUpdateListener(animatorUpdateListener) + + val shrinkAnimation: ValueAnimator + = ValueAnimator.ofInt(largeThickness, standardThickness) + shrinkAnimation.duration = IMPORTANCE_ANIM_SHRINK_DURATION + shrinkAnimation.startDelay = IMPORTANCE_ANIM_SHRINK_DELAY + shrinkAnimation.interpolator = OVERSHOOT + shrinkAnimation.addUpdateListener(animatorUpdateListener) + shrinkAnimation.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + // Shrink the badge bg so that it doesn't peek behind the animation + bg.setSize(baseSize, baseSize); + conversationIconBadgeBg.invalidate(); + } + + override fun onAnimationEnd(animation: Animator?) { + // Reset bg back to normal size + bg.setSize(bgSize, bgSize); + conversationIconBadgeBg.invalidate(); + + } + }) + + val anims = AnimatorSet() + anims.startDelay = IMPORTANCE_ANIM_DELAY + anims.playSequentially(growAnimation, shrinkAnimation) val gapWidth = dialog.context.getResources().getDimensionPixelSize( R.dimen.conversation_onboarding_bullet_gap_width) @@ -180,6 +260,7 @@ class PriorityOnboardingDialogController @Inject constructor( height = WRAP_CONTENT } } + anims.start() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index 4c9cb209424a..c747a7c300b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java @@ -262,7 +262,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { stack.push(mView); while (!stack.isEmpty()) { View child = stack.pop(); - if (child instanceof ImageView) { + if (child instanceof ImageView + // Skip the importance ring for conversations, disabled cropping is needed for + // its animation + && child.getId() != com.android.internal.R.id.conversation_icon_badge_ring) { ((ImageView) child).setCropToPadding(true); } else if (child instanceof ViewGroup){ ViewGroup group = (ViewGroup) child; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 325c8c0e506a..b11ed23c3aeb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -146,6 +146,7 @@ import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener; import com.android.systemui.statusbar.phone.NotificationIconAreaController; @@ -6633,6 +6634,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd MetricsEvent.ACTION_LS_SHADE, (int) (dragLengthY / mDisplayMetrics.density), 0 /* velocityDp - N/A */); + mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_PULL_SHADE_OPEN); if (!mAmbientState.isDozing() || startingChild != null) { // We have notifications, go to locked shade. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java index 8c3420a522ec..83d398d3e7ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java @@ -20,6 +20,9 @@ import android.metrics.LogMaker; import android.util.ArrayMap; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; import com.android.systemui.EventLogConstants; @@ -34,6 +37,56 @@ import javax.inject.Singleton; */ @Singleton public class LockscreenGestureLogger { + + /** + * Contains Lockscreen related Westworld UiEvent enums. + */ + public enum LockscreenUiEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "Lockscreen > Pull shade open") + LOCKSCREEN_PULL_SHADE_OPEN(539), + + @UiEvent(doc = "Lockscreen > Tap on lock, locks phone") + LOCKSCREEN_LOCK_TAP(540), + + @UiEvent(doc = "Lockscreen > Swipe down to open quick settings") + LOCKSCREEN_QUICK_SETTINGS_OPEN(541), + + @UiEvent(doc = "Swipe down to open quick settings when unlocked") + LOCKSCREEN_UNLOCKED_QUICK_SETTINGS_OPEN(542), + + @UiEvent(doc = "Lockscreen > Tap on lock, shows hint") + LOCKSCREEN_LOCK_SHOW_HINT(543), + + @UiEvent(doc = "Notification shade > Tap to open quick settings") + LOCKSCREEN_NOTIFICATION_SHADE_QUICK_SETTINGS_OPEN(544), + + @UiEvent(doc = "Lockscreen > Dialer") + LOCKSCREEN_DIALER(545), + + @UiEvent(doc = "Lockscreen > Camera") + LOCKSCREEN_CAMERA(546), + + @UiEvent(doc = "Lockscreen > Unlock gesture") + LOCKSCREEN_UNLOCK(547), + + @UiEvent(doc = "Lockscreen > Tap on notification, false touch rejection") + LOCKSCREEN_NOTIFICATION_FALSE_TOUCH(548), + + @UiEvent(doc = "Expand the notification panel while unlocked") + LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND(549); + + private final int mId; + + LockscreenUiEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + private ArrayMap<Integer, Integer> mLegacyMap; private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); @@ -55,6 +108,13 @@ public class LockscreenGestureLogger { } /** + * Logs {@link LockscreenUiEvent}. + */ + public void log(LockscreenUiEvent lockscreenUiEvent) { + new UiEventLoggerImpl().log(lockscreenUiEvent); + } + + /** * Record the location of a swipe gesture, expressed as percentages of the whole screen * @param category the action * @param xPercent x-location / width * 100 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java index 8a5c8b0898bc..5d3910b4c415 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -436,6 +437,7 @@ public class LockscreenLockIconController { private boolean handleLongClick(View view) { mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK, 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); + mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP); mKeyguardIndicationController.showTransientIndication( R.string.keyguard_indication_trust_disabled); mKeyguardUpdateMonitor.onLockIconPressed(); 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 26e80adafd3d..8954d98d0201 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -59,6 +59,7 @@ import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.RectF; import android.hardware.display.DisplayManager; import android.inputmethodservice.InputMethodService; import android.net.Uri; @@ -83,6 +84,7 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.view.WindowInsetsController.Appearance; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; @@ -94,6 +96,8 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.LatencyTracker; import com.android.internal.view.AppearanceRegion; @@ -217,11 +221,31 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback * original handle hidden and we'll flip the visibilities once the * {@link #mTasksFrozenListener} fires */ - private NavigationHandle mOrientationHandle; + private VerticalNavigationHandle mOrientationHandle; private WindowManager.LayoutParams mOrientationParams; private int mStartingQuickSwitchRotation; private int mCurrentRotation; private boolean mFixedRotationEnabled; + private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener; + private UiEventLogger mUiEventLogger; + + @com.android.internal.annotations.VisibleForTesting + public enum NavBarActionEvent implements UiEventLogger.UiEventEnum { + + @UiEvent(doc = "Assistant invoked via home button long press.") + NAVBAR_ASSIST_LONGPRESS(550); + + private final int mId; + + NavBarActionEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } /** Only for default display */ @Nullable @@ -364,7 +388,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback ShadeController shadeController, NotificationRemoteInputManager notificationRemoteInputManager, SystemActions systemActions, - @Main Handler mainHandler) { + @Main Handler mainHandler, + UiEventLogger uiEventLogger) { mAccessibilityManagerWrapper = accessibilityManagerWrapper; mDeviceProvisionedController = deviceProvisionedController; mStatusBarStateController = statusBarStateController; @@ -384,6 +409,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mRecentsOptional = recentsOptional; mSystemActions = systemActions; mHandler = mainHandler; + mUiEventLogger = uiEventLogger; } // ----- Fragment Lifecycle Callbacks ----- @@ -519,6 +545,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback getContext().getSystemService(DisplayManager.class).unregisterDisplayListener(this); getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener); mWindowManager.removeView(mOrientationHandle); + mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener( + mOrientationHandleGlobalLayoutListener); } } @@ -573,6 +601,20 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback PixelFormat.TRANSLUCENT); mWindowManager.addView(mOrientationHandle, mOrientationParams); mOrientationHandle.setVisibility(View.GONE); + mOrientationHandleGlobalLayoutListener = + () -> { + if (mStartingQuickSwitchRotation == -1) { + return; + } + + RectF boundsOnScreen = mOrientationHandle.computeHomeHandleBounds(); + mOrientationHandle.mapRectFromViewToScreenCoords(boundsOnScreen, true); + Rect boundsRounded = new Rect(); + boundsOnScreen.roundOut(boundsRounded); + mNavigationBarView.setOrientedHandleSamplingRegion(boundsRounded); + }; + mOrientationHandle.getViewTreeObserver().addOnGlobalLayoutListener( + mOrientationHandleGlobalLayoutListener); } private void orientSecondaryHomeHandle() { @@ -621,6 +663,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback } if (mNavigationBarView != null) { mNavigationBarView.setVisibility(View.VISIBLE); + mNavigationBarView.setOrientedHandleSamplingRegion(null); } } @@ -988,6 +1031,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback return false; } mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS); + mUiEventLogger.log(NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS); Bundle args = new Bundle(); args.putInt( AssistManager.INVOCATION_TYPE_KEY, AssistManager.INVOCATION_HOME_BUTTON_LONG_PRESS); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 6b37ac317cdd..4821d8c46ed4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -35,6 +35,7 @@ import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.DrawableRes; +import android.annotation.Nullable; import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; @@ -158,6 +159,14 @@ public class NavigationBarView extends FrameLayout implements */ private ScreenPinningNotify mScreenPinningNotify; private Rect mSamplingBounds = new Rect(); + /** + * When quickswitching between apps of different orientations, we draw a secondary home handle + * in the position of the first app's orientation. This rect represents the region of that + * home handle so we can apply the correct light/dark luma on that. + * @see {@link NavigationBarFragment#mOrientationHandle} + */ + @Nullable + private Rect mOrientedHandleSamplingRegion; private class NavTransitionListener implements TransitionListener { private boolean mBackTransitioning; @@ -327,6 +336,10 @@ public class NavigationBarView extends FrameLayout implements @Override public Rect getSampledRegion(View sampledView) { + if (mOrientedHandleSamplingRegion != null) { + return mOrientedHandleSamplingRegion; + } + updateSamplingRect(); return mSamplingBounds; } @@ -897,6 +910,11 @@ public class NavigationBarView extends FrameLayout implements } } + void setOrientedHandleSamplingRegion(Rect orientedHandleSamplingRegion) { + mOrientedHandleSamplingRegion = orientedHandleSamplingRegion; + mRegionSamplingHelper.updateSamplingRect(); + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); @@ -1190,6 +1208,8 @@ public class NavigationBarView extends FrameLayout implements mIsVertical ? "true" : "false", getLightTransitionsController().getCurrentDarkIntensity())); + pw.println(" mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion); + dumpButton(pw, "back", getBackButton()); dumpButton(pw, "home", getHomeButton()); dumpButton(pw, "rcnt", getRecentsButton()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 8889510cde28..d884bdd47930 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -104,9 +104,9 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.stack.AnimationProperties; -import com.android.systemui.statusbar.notification.stack.MediaHeaderView; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -2474,6 +2474,8 @@ public class NotificationPanelViewController extends PanelViewController { } else { mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT, 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); + mLockscreenGestureLogger + .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT); startUnlockHintAnimation(); } } @@ -3257,7 +3259,7 @@ public class NotificationPanelViewController extends PanelViewController { int velocityDp = Math.abs((int) (vel / displayDensity)); if (start) { mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp); - + mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_DIALER); mFalsingManager.onLeftAffordanceOn(); if (mFalsingManager.shouldEnforceBouncer()) { mStatusBar.executeRunnableDismissingKeyguard( @@ -3272,6 +3274,7 @@ public class NotificationPanelViewController extends PanelViewController { mLastCameraLaunchSource)) { mLockscreenGestureLogger.write( MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp); + mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_CAMERA); } mFalsingManager.onCameraOn(); if (mFalsingManager.shouldEnforceBouncer()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 57d36fcafa15..a902e1b0c960 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -47,6 +47,7 @@ import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; import com.android.systemui.statusbar.policy.KeyguardStateController; import java.io.FileDescriptor; @@ -319,6 +320,8 @@ public abstract class PanelViewController { mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND, (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot); + mLockscreenGestureLogger + .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND); } protected void maybeVibrateOnOpening() { @@ -378,6 +381,7 @@ public abstract class PanelViewController { int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity); int velocityDp = (int) Math.abs(vel / displayDensity); mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp); + mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK); } fling(vel, expand, isFalseTouch(x, y)); onTrackingStopped(expand); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 84da35b63d0a..45f0c49a4fd4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -72,6 +72,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -374,6 +375,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, mLockscreenGestureLogger.write( MetricsEvent.ACTION_LS_NOTE, 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); + mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_NOTIFICATION_FALSE_TOUCH); mNotificationPanel.showTransientIndication(R.string.notification_tap_again); ActivatableNotificationView previousView = mNotificationPanel.getActivatedChild(); if (previousView != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java index a15ca9532a88..0cdf1d32d6a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/VerticalNavigationHandle.java @@ -18,12 +18,14 @@ package com.android.systemui.statusbar.phone; import android.content.Context; import android.graphics.Canvas; +import android.graphics.RectF; import com.android.systemui.R; /** Temporarily shown view when using QuickSwitch to switch between apps of different rotations */ public class VerticalNavigationHandle extends NavigationHandle { private final int mWidth; + private final RectF mTmpBoundsRectF = new RectF(); public VerticalNavigationHandle(Context context) { super(context); @@ -32,16 +34,21 @@ public class VerticalNavigationHandle extends NavigationHandle { @Override protected void onDraw(Canvas canvas) { + canvas.drawRoundRect(computeHomeHandleBounds(), mRadius, mRadius, mPaint); + } + + RectF computeHomeHandleBounds() { int left; int top; int bottom; int right; - + int topStart = getLocationOnScreen()[1]; int radiusOffset = mRadius * 2; right = getWidth() - mBottom; - top = getHeight() / 2 - (mWidth / 2); /* (height of screen / 2) - (height of bar / 2) */ + top = getHeight() / 2 - (mWidth / 2) - (topStart / 2); left = getWidth() - mBottom - radiusOffset; - bottom = getHeight() / 2 + (mWidth / 2); - canvas.drawRoundRect(left, top, right, bottom, mRadius, mRadius, mPaint); + bottom = top + mWidth; + mTmpBoundsRectF.set(left, top, right, bottom); + return mTmpBoundsRectF; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 58f5d2a5b43f..0ca8ef009173 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -87,7 +87,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { private boolean mHasOvalBg = false; @VisibleForTesting - public enum NavBarActionsEvent implements UiEventLogger.UiEventEnum { + public enum NavBarButtonEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "The home button was pressed in the navigation bar.") NAVBAR_HOME_BUTTON_TAP(533), @@ -111,7 +111,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { private final int mId; - NavBarActionsEvent(int id) { + NavBarButtonEvent(int id) { mId = id; } @@ -368,7 +368,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { private void logSomePresses(int action, int flags) { boolean longPressSet = (flags & KeyEvent.FLAG_LONG_PRESS) != 0; - NavBarActionsEvent uiEvent = NavBarActionsEvent.NONE; + NavBarButtonEvent uiEvent = NavBarButtonEvent.NONE; if (action == MotionEvent.ACTION_UP && mLongClicked) { return; // don't log the up after a long press } @@ -382,21 +382,21 @@ public class KeyButtonView extends ImageView implements ButtonInterface { switch(mCode) { case KeyEvent.KEYCODE_BACK: uiEvent = longPressSet - ? NavBarActionsEvent.NAVBAR_BACK_BUTTON_LONGPRESS - : NavBarActionsEvent.NAVBAR_BACK_BUTTON_TAP; + ? NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS + : NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP; break; case KeyEvent.KEYCODE_HOME: uiEvent = longPressSet - ? NavBarActionsEvent.NAVBAR_HOME_BUTTON_LONGPRESS - : NavBarActionsEvent.NAVBAR_HOME_BUTTON_TAP; + ? NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS + : NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP; break; case KeyEvent.KEYCODE_APP_SWITCH: uiEvent = longPressSet - ? NavBarActionsEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS - : NavBarActionsEvent.NAVBAR_OVERVIEW_BUTTON_TAP; + ? NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS + : NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP; break; } - if (uiEvent != NavBarActionsEvent.NONE) { + if (uiEvent != NavBarButtonEvent.NONE) { mUiEventLogger.log(uiEvent); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java index 914868301d72..8b85a0961463 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/micdisclosure/AudioRecordingDisclosureBar.java @@ -29,6 +29,8 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.PixelFormat; +import android.provider.Settings; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.view.Gravity; @@ -63,6 +65,9 @@ public class AudioRecordingDisclosureBar implements // CtsSystemUiHostTestCases:TvMicrophoneCaptureIndicatorTest private static final String LAYOUT_PARAMS_TITLE = "MicrophoneCaptureIndicator"; + private static final String EXEMPT_PACKAGES_LIST = "sysui_mic_disclosure_exempt"; + private static final String FORCED_PACKAGES_LIST = "sysui_mic_disclosure_forced"; + @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"STATE_"}, value = { STATE_NOT_SHOWN, @@ -134,6 +139,8 @@ public class AudioRecordingDisclosureBar implements mExemptPackages = new ArraySet<>( Arrays.asList(mContext.getResources().getStringArray( R.array.audio_recording_disclosure_exempt_apps))); + mExemptPackages.addAll(Arrays.asList(getGlobalStringArray(EXEMPT_PACKAGES_LIST))); + mExemptPackages.removeAll(Arrays.asList(getGlobalStringArray(FORCED_PACKAGES_LIST))); mAudioActivityObservers = new AudioActivityObserver[]{ new RecordAudioAppOpObserver(mContext, this), @@ -141,6 +148,11 @@ public class AudioRecordingDisclosureBar implements }; } + private String[] getGlobalStringArray(String setting) { + String result = Settings.Global.getString(mContext.getContentResolver(), setting); + return TextUtils.isEmpty(result) ? new String[0] : result.split(","); + } + @UiThread @Override public void onAudioActivityStateChange(boolean active, String packageName) { diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt index da7953d27f59..5a2b064c5389 100644 --- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt @@ -32,6 +32,7 @@ import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringForce import com.android.systemui.util.animation.PhysicsAnimator +import kotlin.math.abs import kotlin.math.hypot /** @@ -231,11 +232,11 @@ abstract class MagnetizedObject<T : Any>( var flingUnstuckFromTargetMinVelocity = 1000f /** - * Sets the maximum velocity above which the object will not stick to the target. Even if the + * Sets the maximum X velocity above which the object will not stick to the target. Even if the * object is dragged through the magnetic field, it will not stick to the target until the - * velocity is below this value. + * horizontal velocity is below this value. */ - var stickToTargetMaxVelocity = 2000f + var stickToTargetMaxXVelocity = 2000f /** * Enable or disable haptic vibration effects when the object interacts with the magnetic field. @@ -363,7 +364,7 @@ abstract class MagnetizedObject<T : Any>( // If the object is moving too quickly within the magnetic field, do not stick it. This // only applies to objects newly stuck to a target. If the object is moved into a new // target, it wasn't moving at all (since it was stuck to the previous one). - if (objectNewlyStuckToTarget && hypot(velX, velY) > stickToTargetMaxVelocity) { + if (objectNewlyStuckToTarget && abs(velX) > stickToTargetMaxXVelocity) { return false } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index c89f6c2597d0..1e48b990b19d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -44,7 +44,6 @@ import android.app.IActivityManager; import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; -import android.content.res.Resources; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.face.FaceManager; import android.os.Handler; @@ -59,7 +58,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; -import com.android.systemui.SystemUIFactory; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; @@ -70,20 +69,16 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationRemoveInterceptor; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; -import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; @@ -93,7 +88,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.util.InjectionInflationController; import com.google.common.collect.ImmutableList; @@ -173,23 +167,18 @@ public class BubbleControllerTest extends SysuiTestCase { @Mock ColorExtractor.GradientColors mGradientColors; @Mock - private Resources mResources; - @Mock private ShadeController mShadeController; @Mock - private NotificationRowComponent mNotificationRowComponent; - @Mock private NotifPipeline mNotifPipeline; @Mock private FeatureFlags mFeatureFlagsOldPipeline; @Mock private DumpManager mDumpManager; @Mock - private LockscreenLockIconController mLockIconController; - @Mock private NotificationShadeWindowView mNotificationShadeWindowView; + @Mock + private IStatusBarService mStatusBarService; - private SuperStatusBarViewFactory mSuperStatusBarViewFactory; private BubbleData mBubbleData; private TestableLooper mTestableLooper; @@ -203,23 +192,6 @@ public class BubbleControllerTest extends SysuiTestCase { mContext.addMockSystemService(FaceManager.class, mFaceManager); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); - mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext, - new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()), - new NotificationRowComponent.Builder() { - @Override - public NotificationRowComponent.Builder activatableNotificationView( - ActivatableNotificationView view) { - return this; - } - - @Override - public NotificationRowComponent build() { - return mNotificationRowComponent; - } - }, - mLockIconController); - - // Bubbles get added to status bar window view mNotificationShadeWindowController = new NotificationShadeWindowController(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardBypassController, mColorExtractor, @@ -282,6 +254,7 @@ public class BubbleControllerTest extends SysuiTestCase { mDataRepository, mSysUiState, mock(INotificationManager.class), + mStatusBarService, mWindowManager); mBubbleController.setExpandListener(mBubbleExpandListener); @@ -327,7 +300,7 @@ public class BubbleControllerTest extends SysuiTestCase { } @Test - public void testPromoteBubble_autoExpand() { + public void testPromoteBubble_autoExpand() throws Exception { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); mBubbleController.removeBubble( @@ -337,13 +310,19 @@ public class BubbleControllerTest extends SysuiTestCase { assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b)); verify(mNotificationEntryManager, never()).performRemoveNotification( eq(mRow.getEntry().getSbn()), anyInt()); - assertFalse(mRow.getEntry().isBubble()); + assertThat(mRow.getEntry().isBubble()).isFalse(); Bubble b2 = mBubbleData.getBubbleInStackWithKey(mRow2.getEntry().getKey()); assertThat(mBubbleData.getSelectedBubble()).isEqualTo(b2); mBubbleController.promoteBubbleFromOverflow(b); - assertThat(mBubbleData.getSelectedBubble()).isEqualTo(b); + + assertThat(b.isBubble()).isTrue(); + assertThat(b.shouldAutoExpand()).isTrue(); + int flags = Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE + | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + verify(mStatusBarService, times(1)).onNotificationBubbleChanged( + eq(b.getKey()), eq(true), eq(flags)); } @Test @@ -446,7 +425,8 @@ public class BubbleControllerTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + true, mRow2.getEntry().getKey()); assertTrue(mSysUiStateBubblesExpanded); @@ -464,9 +444,11 @@ public class BubbleControllerTest extends SysuiTestCase { mRow.getEntry())); // collapse for previous bubble - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + false, mRow2.getEntry().getKey()); // expand for selected bubble - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + true, mRow.getEntry().getKey()); // Collapse mBubbleController.collapseStack(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index 8224c88e6c75..17022da3ecde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -278,7 +278,7 @@ public class BubbleDataTest extends SysuiTestCase { assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_AGED); assertOverflowChangedTo(ImmutableList.of(mBubbleA1)); - Bubble bubbleA1 = mBubbleData.getOrCreateBubble(mEntryA1); + Bubble bubbleA1 = mBubbleData.getOrCreateBubble(mEntryA1, null /* persistedBubble */); bubbleA1.markUpdatedAt(7000L); mBubbleData.notificationEntryUpdated(bubbleA1, false /* suppressFlyout*/, true /* showInShade */); @@ -890,7 +890,7 @@ public class BubbleDataTest extends SysuiTestCase { private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) { setPostTime(entry, postTime); // BubbleController calls this: - Bubble b = mBubbleData.getOrCreateBubble(entry); + Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */); // And then this mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/, true /* showInShade */); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index ead95ca1665e..0be24729dff9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -55,7 +55,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; -import com.android.systemui.SystemUIFactory; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; @@ -64,14 +64,12 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; -import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; @@ -87,7 +85,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.util.InjectionInflationController; import org.junit.Before; import org.junit.Test; @@ -174,8 +171,9 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { private DumpManager mDumpManager; @Mock private LockscreenLockIconController mLockIconController; + @Mock + private IStatusBarService mStatusBarService; - private SuperStatusBarViewFactory mSuperStatusBarViewFactory; private BubbleData mBubbleData; private TestableLooper mTestableLooper; @@ -189,22 +187,6 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mContext.addMockSystemService(FaceManager.class, mFaceManager); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); - mSuperStatusBarViewFactory = new SuperStatusBarViewFactory(mContext, - new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent()), - new NotificationRowComponent.Builder() { - @Override - public NotificationRowComponent.Builder activatableNotificationView( - ActivatableNotificationView view) { - return this; - } - - @Override - public NotificationRowComponent build() { - return mNotificationRowComponent; - } - }, - mLockIconController); - // Bubbles get added to status bar window view mNotificationShadeWindowController = new NotificationShadeWindowController(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, @@ -257,6 +239,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mDataRepository, mSysUiState, mock(INotificationManager.class), + mStatusBarService, mWindowManager); mBubbleController.addNotifCallback(mNotifCallback); mBubbleController.setExpandListener(mBubbleExpandListener); @@ -399,7 +382,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { BubbleStackView stackView = mBubbleController.getStackView(); mBubbleData.setExpanded(true); assertTrue(mBubbleController.isStackExpanded()); - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + true, mRow.getEntry().getKey()); // Last added is the one that is expanded assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); @@ -414,9 +398,11 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mRow.getEntry())); // collapse for previous bubble - verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + false, mRow2.getEntry().getKey()); // expand for selected bubble - verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); + verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged( + true, mRow.getEntry().getKey()); // Collapse mBubbleController.collapseStack(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java index bdb79449ea2a..ab49134ee8c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java @@ -20,6 +20,7 @@ import android.app.INotificationManager; import android.content.Context; import android.view.WindowManager; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -59,13 +60,14 @@ public class TestableBubbleController extends BubbleController { BubbleDataRepository dataRepository, SysUiState sysUiState, INotificationManager notificationManager, + IStatusBarService statusBarService, WindowManager windowManager) { super(context, notificationShadeWindowController, statusBarStateController, shadeController, data, Runnable::run, configurationController, interruptionStateProvider, zenModeController, lockscreenUserManager, groupManager, entryManager, notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, - dataRepository, sysUiState, notificationManager, + dataRepository, sysUiState, notificationManager, statusBarService, windowManager); setInflateSynchronously(true); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java index 536cae4380c4..b7a2633d0d36 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java @@ -82,7 +82,7 @@ public class PipAnimationControllerTest extends SysuiTestCase { @Test public void getAnimator_withBounds_returnBoundsAnimator() { final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, new Rect(), new Rect(), null); + .getAnimator(mLeash, new Rect(), new Rect()); assertEquals("Expect ANIM_TYPE_BOUNDS animation", animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS); @@ -94,12 +94,12 @@ public class PipAnimationControllerTest extends SysuiTestCase { final Rect endValue1 = new Rect(100, 100, 200, 200); final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue1, null); + .getAnimator(mLeash, startValue, endValue1); oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); oldAnimator.start(); final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue2, null); + .getAnimator(mLeash, startValue, endValue2); assertEquals("getAnimator with same type returns same animator", oldAnimator, newAnimator); @@ -129,7 +129,7 @@ public class PipAnimationControllerTest extends SysuiTestCase { final Rect endValue1 = new Rect(100, 100, 200, 200); final Rect endValue2 = new Rect(200, 200, 300, 300); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue1, null); + .getAnimator(mLeash, startValue, endValue1); animator.updateEndValue(endValue2); @@ -141,7 +141,7 @@ public class PipAnimationControllerTest extends SysuiTestCase { final Rect startValue = new Rect(0, 0, 100, 100); final Rect endValue = new Rect(100, 100, 200, 200); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController - .getAnimator(mLeash, startValue, endValue, null); + .getAnimator(mLeash, startValue, endValue); animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new); animator.setPipAnimationCallback(mPipAnimationCallback); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java index f327967ebd73..43aa8fef76a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java @@ -25,6 +25,7 @@ import static android.view.View.VISIBLE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; @@ -162,6 +163,7 @@ public class PartialConversationInfoTest extends SysuiTestCase { @Test public void testBindNotification_SetsName() { + when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Package"); mInfo.bindNotification( mMockPackageManager, mMockINotificationManager, @@ -174,42 +176,13 @@ public class PartialConversationInfoTest extends SysuiTestCase { true, false); final TextView textView = mInfo.findViewById(R.id.name); - assertTrue(textView.getText().toString().contains("title")); + assertTrue(textView.getText().toString().equals("Package")); } - @Test - public void testBindNotification_groupSetsPackageIcon() { - mEntry.getSbn().getNotification().extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, true); - final Drawable iconDrawable = mock(Drawable.class); - when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class))) - .thenReturn(iconDrawable); - mInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mNotificationChannelSet, - mEntry, - null, - true, - false); - final ImageView iconView = mInfo.findViewById(R.id.conversation_icon); - assertEquals(iconDrawable, iconView.getDrawable()); - } @Test - public void testBindNotification_notGroupSetsMessageIcon() { - Notification n = new Notification.Builder(mContext, TEST_CHANNEL_NAME) - .setStyle(new Notification.MessagingStyle( - new Person.Builder().setName("me").build()) - .addMessage(new Notification.MessagingStyle.Message("hello", 0, - new Person.Builder().setName("friend").setIcon(mIcon).build()))) - .build(); - mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0, - n, UserHandle.CURRENT, null, 0); - mEntry.setSbn(mSbn); - mEntry.getSbn().getNotification().extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, false); + public void testBindNotification_setsIcon() { + when(mMockPackageManager.getApplicationIcon((ApplicationInfo) any())).thenReturn(mDrawable); mInfo.bindNotification( mMockPackageManager, mMockINotificationManager, @@ -221,7 +194,7 @@ public class PartialConversationInfoTest extends SysuiTestCase { null, true, false); - final ImageView iconView = mInfo.findViewById(R.id.conversation_icon); + final ImageView iconView = mInfo.findViewById(R.id.icon); assertEquals(mDrawable.hashCode() + "", mDrawable, iconView.getDrawable()); } @@ -270,64 +243,6 @@ public class PartialConversationInfoTest extends SysuiTestCase { } @Test - public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception { - mInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mNotificationChannelSet, - mEntry, - null, - true, - false); - final TextView groupNameView = mInfo.findViewById(R.id.group_name); - assertEquals(GONE, groupNameView.getVisibility()); - } - - @Test - public void testBindNotification_SetsGroupNameIfNonNull() throws Exception { - mNotificationChannel.setGroup("test_group_id"); - final NotificationChannelGroup notificationChannelGroup = - new NotificationChannelGroup("test_group_id", "Test Group Name"); - when(mMockINotificationManager.getNotificationChannelGroupForPackage( - eq("test_group_id"), eq(TEST_PACKAGE_NAME), eq(TEST_UID))) - .thenReturn(notificationChannelGroup); - mInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mNotificationChannelSet, - mEntry, - null, - true, - false); - final TextView groupNameView = mInfo.findViewById(R.id.group_name); - assertEquals(View.VISIBLE, groupNameView.getVisibility()); - assertEquals("Test Group Name", groupNameView.getText()); - } - - @Test - public void testBindNotification_SetsTextChannelName() { - mInfo.bindNotification( - mMockPackageManager, - mMockINotificationManager, - mChannelEditorDialogController, - TEST_PACKAGE_NAME, - mNotificationChannel, - mNotificationChannelSet, - mEntry, - null, - true, - false); - final TextView textView = mInfo.findViewById(R.id.parent_channel_name); - assertEquals(TEST_CHANNEL_NAME, textView.getText()); - } - - @Test public void testBindNotification_SetsOnClickListenerForSettings() { final CountDownLatch latch = new CountDownLatch(1); mInfo.bindNotification( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java index a77f8c649f30..00cbddc87726 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java @@ -21,6 +21,8 @@ import static android.inputmethodservice.InputMethodService.IME_VISIBLE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; +import static com.android.systemui.statusbar.phone.NavigationBarFragment.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -28,6 +30,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -59,6 +62,7 @@ import android.view.accessibility.AccessibilityManager.AccessibilityServicesStat import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.Dependency; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.SysuiTestableContext; @@ -105,6 +109,8 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { private Recents mRecents; @Mock private SystemActions mSystemActions; + @Mock + private UiEventLogger mUiEventLogger; private AccessibilityManagerWrapper mAccessibilityWrapper = new AccessibilityManagerWrapper(mContext) { @@ -187,6 +193,8 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { mFragments.dispatchResume(); processAllMessages(); navigationBarFragment.onHomeLongClick(navigationBarFragment.getView()); + + verify(mUiEventLogger, times(1)).log(NAVBAR_ASSIST_LONGPRESS); } @Test @@ -242,6 +250,7 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { protected Fragment instantiate(Context context, String className, Bundle arguments) { DeviceProvisionedController deviceProvisionedController = mock(DeviceProvisionedController.class); + when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true); assertNotNull(mAccessibilityWrapper); return new NavigationBarFragment( context.getDisplayId() == DEFAULT_DISPLAY ? mAccessibilityWrapper @@ -261,7 +270,8 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { mock(ShadeController.class), mock(NotificationRemoteInputManager.class), mock(SystemActions.class), - mHandler); + mHandler, + mUiEventLogger); } private class HostCallbacksForExternalDisplay extends @@ -319,6 +329,7 @@ public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { mock(LightBarTransitionsController.class)); when(view.getRotationButtonController()).thenReturn( mock(RotationButtonController.class)); + when(view.isRecentsButtonVisible()).thenReturn(true); return view; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java index 5e9d592ab773..d52d6860bf42 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java @@ -23,12 +23,12 @@ import static android.view.KeyEvent.KEYCODE_APP_SWITCH; import static android.view.KeyEvent.KEYCODE_BACK; import static android.view.KeyEvent.KEYCODE_HOME; -import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_BACK_BUTTON_LONGPRESS; -import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_BACK_BUTTON_TAP; -import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_HOME_BUTTON_LONGPRESS; -import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_HOME_BUTTON_TAP; -import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS; -import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarActionsEvent.NAVBAR_OVERVIEW_BUTTON_TAP; +import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_LONGPRESS; +import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_BACK_BUTTON_TAP; +import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_LONGPRESS; +import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_HOME_BUTTON_TAP; +import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS; +import static com.android.systemui.statusbar.policy.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP; import static junit.framework.Assert.assertEquals; @@ -140,11 +140,11 @@ public class KeyButtonViewTest extends SysuiTestCase { } private void checkmetrics(int code, int action, int flag, - KeyButtonView.NavBarActionsEvent expected) { + KeyButtonView.NavBarButtonEvent expected) { mKeyButtonView.setCode(code); mKeyButtonView.sendEvent(action, flag); if (expected == null) { - verify(mUiEventLogger, never()).log(any(KeyButtonView.NavBarActionsEvent.class)); + verify(mUiEventLogger, never()).log(any(KeyButtonView.NavBarButtonEvent.class)); } else { verify(mUiEventLogger, times(1)).log(expected); } diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index d07a70c1af9b..32e2b04e8e4f 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -25,7 +25,7 @@ java_defaults { ], static_libs: [ "androidx.annotation_annotation", - "netd_aidl_interface-V3-java", + "netd_aidl_interface-java", "netlink-client", "networkstack-aidl-interfaces-java", "android.hardware.tetheroffload.config-V1.0-java", diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index 3fd9ee9a330b..1671dda4bd57 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -33,7 +33,6 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; -import android.net.TetherOffloadRuleParcel; import android.net.TetheredClient; import android.net.TetheringManager; import android.net.TetheringRequestParcel; @@ -65,6 +64,8 @@ import androidx.annotation.Nullable; import com.android.internal.util.MessageUtils; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.networkstack.tethering.BpfCoordinator; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.PrivateAddressCoordinator; import java.io.IOException; @@ -76,7 +77,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; import java.util.Random; @@ -225,6 +225,8 @@ public class IpServer extends StateMachine { private final SharedLog mLog; private final INetd mNetd; + @NonNull + private final BpfCoordinator mBpfCoordinator; private final Callback mCallback; private final InterfaceController mInterfaceCtrl; private final PrivateAddressCoordinator mPrivateAddressCoordinator; @@ -269,43 +271,6 @@ public class IpServer extends StateMachine { } } - static class Ipv6ForwardingRule { - public final int upstreamIfindex; - public final int downstreamIfindex; - public final Inet6Address address; - public final MacAddress srcMac; - public final MacAddress dstMac; - - Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address, - MacAddress srcMac, MacAddress dstMac) { - this.upstreamIfindex = upstreamIfindex; - this.downstreamIfindex = downstreamIfIndex; - this.address = address; - this.srcMac = srcMac; - this.dstMac = dstMac; - } - - public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) { - return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac, - dstMac); - } - - // Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream() - // would be error-prone due to generated stable AIDL classes not having a copy constructor. - public TetherOffloadRuleParcel toTetherOffloadRuleParcel() { - final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel(); - parcel.inputInterfaceIndex = upstreamIfindex; - parcel.outputInterfaceIndex = downstreamIfindex; - parcel.destination = address.getAddress(); - parcel.prefixLength = 128; - parcel.srcL2Address = srcMac.toByteArray(); - parcel.dstL2Address = dstMac.toByteArray(); - return parcel; - } - } - private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules = - new LinkedHashMap<>(); - private final IpNeighborMonitor mIpNeighborMonitor; private LinkAddress mIpv4Address; @@ -314,11 +279,13 @@ public class IpServer extends StateMachine { // object. It helps to reduce the arguments of the constructor. public IpServer( String ifaceName, Looper looper, int interfaceType, SharedLog log, - INetd netd, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload, + INetd netd, @NonNull BpfCoordinator coordinator, Callback callback, + boolean usingLegacyDhcp, boolean usingBpfOffload, PrivateAddressCoordinator addressCoordinator, Dependencies deps) { super(ifaceName, looper); mLog = log.forSubComponent(ifaceName); mNetd = netd; + mBpfCoordinator = coordinator; mCallback = callback; mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog); mIfaceName = ifaceName; @@ -749,6 +716,14 @@ public class IpServer extends StateMachine { } upstreamIfindex = mDeps.getIfindex(upstreamIface); + + // Add upstream index to name mapping for the tether stats usage in the coordinator. + // Although this mapping could be added by both class Tethering and IpServer, adding + // mapping from IpServer guarantees that the mapping is added before the adding + // forwarding rules. That is because there are different state machines in both + // classes. It is hard to guarantee the link property update order between multiple + // state machines. + mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfindex, upstreamIface); } // If v6only is null, we pass in null to setRaParams(), which handles @@ -864,43 +839,29 @@ public class IpServer extends StateMachine { // TODO: Perhaps remove this protection check. if (!mUsingBpfOffload) return; - try { - mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel()); - mIpv6ForwardingRules.put(rule.address, rule); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Could not add IPv6 downstream rule: ", e); - } + mBpfCoordinator.tetherOffloadRuleAdd(this, rule); } - private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) { - // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF - // offload is disabled. Add this check just in case. + private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule) { // TODO: Perhaps remove this protection check. + // See the related comment in #addIpv6ForwardingRule. if (!mUsingBpfOffload) return; - try { - mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel()); - if (removeFromMap) { - mIpv6ForwardingRules.remove(rule.address); - } - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Could not remove IPv6 downstream rule: ", e); - } + mBpfCoordinator.tetherOffloadRuleRemove(this, rule); } private void clearIpv6ForwardingRules() { - for (Ipv6ForwardingRule rule : mIpv6ForwardingRules.values()) { - removeIpv6ForwardingRule(rule, false /*removeFromMap*/); - } - mIpv6ForwardingRules.clear(); + if (!mUsingBpfOffload) return; + + mBpfCoordinator.tetherOffloadRuleClear(this); } - // Convenience method to replace a rule with the same rule on a new upstream interface. - // Allows replacing the rules in one iteration pass without ConcurrentModificationExceptions. - // Relies on the fact that rules are in a map indexed by IP address. - private void updateIpv6ForwardingRule(Ipv6ForwardingRule rule, int newIfindex) { - addIpv6ForwardingRule(rule.onNewUpstream(newIfindex)); - removeIpv6ForwardingRule(rule, false /*removeFromMap*/); + private void updateIpv6ForwardingRule(int newIfindex) { + // TODO: Perhaps remove this protection check. + // See the related comment in #addIpv6ForwardingRule. + if (!mUsingBpfOffload) return; + + mBpfCoordinator.tetherOffloadRuleUpdate(this, newIfindex); } // Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream @@ -916,9 +877,7 @@ public class IpServer extends StateMachine { // If the upstream interface has changed, remove all rules and re-add them with the new // upstream interface. if (prevUpstreamIfindex != upstreamIfindex) { - for (Ipv6ForwardingRule rule : mIpv6ForwardingRules.values()) { - updateIpv6ForwardingRule(rule, upstreamIfindex); - } + updateIpv6ForwardingRule(upstreamIfindex); } // If we're here to process a NeighborEvent, do so now. @@ -938,7 +897,7 @@ public class IpServer extends StateMachine { if (e.isValid()) { addIpv6ForwardingRule(rule); } else { - removeIpv6ForwardingRule(rule, true /*removeFromMap*/); + removeIpv6ForwardingRule(rule); } } diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java index dd67dddae1cd..b17b4ba77cfb 100644 --- a/packages/Tethering/src/android/net/util/TetheringUtils.java +++ b/packages/Tethering/src/android/net/util/TetheringUtils.java @@ -15,19 +15,94 @@ */ package android.net.util; +import android.net.TetherStatsParcel; import android.net.TetheringRequestParcel; +import androidx.annotation.NonNull; + import java.io.FileDescriptor; import java.net.SocketException; import java.util.Objects; /** - * Native methods for tethering utilization. + * The classes and the methods for tethering utilization. * * {@hide} */ public class TetheringUtils { /** + * The object which records offload Tx/Rx forwarded bytes/packets. + * TODO: Replace the inner class ForwardedStats of class OffloadHardwareInterface with + * this class as well. + */ + public static class ForwardedStats { + public final long rxBytes; + public final long rxPackets; + public final long txBytes; + public final long txPackets; + + public ForwardedStats() { + rxBytes = 0; + rxPackets = 0; + txBytes = 0; + txPackets = 0; + } + + public ForwardedStats(long rxBytes, long txBytes) { + this.rxBytes = rxBytes; + this.rxPackets = 0; + this.txBytes = txBytes; + this.txPackets = 0; + } + + public ForwardedStats(long rxBytes, long rxPackets, long txBytes, long txPackets) { + this.rxBytes = rxBytes; + this.rxPackets = rxPackets; + this.txBytes = txBytes; + this.txPackets = txPackets; + } + + public ForwardedStats(@NonNull TetherStatsParcel tetherStats) { + rxBytes = tetherStats.rxBytes; + rxPackets = tetherStats.rxPackets; + txBytes = tetherStats.txBytes; + txPackets = tetherStats.txPackets; + } + + public ForwardedStats(@NonNull ForwardedStats other) { + rxBytes = other.rxBytes; + rxPackets = other.rxPackets; + txBytes = other.txBytes; + txPackets = other.txPackets; + } + + /** Add Tx/Rx bytes/packets and return the result as a new object. */ + @NonNull + public ForwardedStats add(@NonNull ForwardedStats other) { + return new ForwardedStats(rxBytes + other.rxBytes, rxPackets + other.rxPackets, + txBytes + other.txBytes, txPackets + other.txPackets); + } + + /** Subtract Tx/Rx bytes/packets and return the result as a new object. */ + @NonNull + public ForwardedStats subtract(@NonNull ForwardedStats other) { + // TODO: Perhaps throw an exception if any negative difference value just in case. + final long rxBytesDiff = Math.max(rxBytes - other.rxBytes, 0); + final long rxPacketsDiff = Math.max(rxPackets - other.rxPackets, 0); + final long txBytesDiff = Math.max(txBytes - other.txBytes, 0); + final long txPacketsDiff = Math.max(txPackets - other.txPackets, 0); + return new ForwardedStats(rxBytesDiff, rxPacketsDiff, txBytesDiff, txPacketsDiff); + } + + /** Returns the string representation of this object. */ + @NonNull + public String toString() { + return String.format("ForwardedStats(rxb: %d, rxp: %d, txb: %d, txp: %d)", rxBytes, + rxPackets, txBytes, txPackets); + } + } + + /** * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements. * @param fd the socket's {@link FileDescriptor}. * @param ifIndex the interface index. diff --git a/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java new file mode 100644 index 000000000000..fc27b6add052 --- /dev/null +++ b/packages/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -0,0 +1,637 @@ +/* + * 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.networkstack.tethering; + +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static android.net.NetworkStats.UID_TETHERING; +import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; + +import android.app.usage.NetworkStatsManager; +import android.net.INetd; +import android.net.MacAddress; +import android.net.NetworkStats; +import android.net.NetworkStats.Entry; +import android.net.TetherOffloadRuleParcel; +import android.net.TetherStatsParcel; +import android.net.ip.IpServer; +import android.net.netstats.provider.NetworkStatsProvider; +import android.net.util.SharedLog; +import android.net.util.TetheringUtils.ForwardedStats; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.net.Inet6Address; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Objects; + +/** + * This coordinator is responsible for providing BPF offload relevant functionality. + * - Get tethering stats. + * - Set data limit. + * - Set global alert. + * - Add/remove forwarding rules. + * + * @hide + */ +public class BpfCoordinator { + private static final String TAG = BpfCoordinator.class.getSimpleName(); + @VisibleForTesting + static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; // TODO: Make it customizable. + + @VisibleForTesting + enum StatsType { + STATS_PER_IFACE, + STATS_PER_UID, + } + + @NonNull + private final Handler mHandler; + @NonNull + private final INetd mNetd; + @NonNull + private final SharedLog mLog; + @NonNull + private final Dependencies mDeps; + @Nullable + private final BpfTetherStatsProvider mStatsProvider; + + // Tracks whether BPF tethering is started or not. This is set by tethering before it + // starts the first IpServer and is cleared by tethering shortly before the last IpServer + // is stopped. Note that rule updates (especially deletions, but sometimes additions as + // well) may arrive when this is false. If they do, they must be communicated to netd. + // Changes in data limits may also arrive when this is false, and if they do, they must + // also be communicated to netd. + private boolean mPollingStarted = false; + + // Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert + // quota is interface independent and global for tether offload. + private long mRemainingAlertQuota = QUOTA_UNLIMITED; + + // Maps upstream interface index to offloaded traffic statistics. + // Always contains the latest total bytes/packets, since each upstream was started, received + // from the BPF maps for each interface. + private final SparseArray<ForwardedStats> mStats = new SparseArray<>(); + + // Maps upstream interface names to interface quotas. + // Always contains the latest value received from the framework for each interface, regardless + // of whether offload is currently running (or is even supported) on that interface. Only + // includes interfaces that have a quota set. Note that this map is used for storing the quota + // which is set from the service. Because the service uses the interface name to present the + // interface, this map uses the interface name to be the mapping index. + private final HashMap<String, Long> mInterfaceQuotas = new HashMap<>(); + + // Maps upstream interface index to interface names. + // Store all interface name since boot. Used for lookup what interface name it is from the + // tether stats got from netd because netd reports interface index to present an interface. + // TODO: Remove the unused interface name. + private final SparseArray<String> mInterfaceNames = new SparseArray<>(); + + // Map of downstream rule maps. Each of these maps represents the IPv6 forwarding rules for a + // given downstream. Each map: + // - Is owned by the IpServer that is responsible for that downstream. + // - Must only be modified by that IpServer. + // - Is created when the IpServer adds its first rule, and deleted when the IpServer deletes + // its last rule (or clears its rules). + // TODO: Perhaps seal the map and rule operations which communicates with netd into a class. + // TODO: Does this need to be a LinkedHashMap or can it just be a HashMap? Also, could it be + // a ConcurrentHashMap, in order to avoid the copies in tetherOffloadRuleClear + // and tetherOffloadRuleUpdate? + // TODO: Perhaps use one-dimensional map and access specific downstream rules via downstream + // index. For doing that, IpServer must guarantee that it always has a valid IPv6 downstream + // interface index while calling function to clear all rules. IpServer may be calling clear + // rules function without a valid IPv6 downstream interface index even if it may have one + // before. IpServer would need to call getInterfaceParams() in the constructor instead of when + // startIpv6() is called, and make mInterfaceParams final. + private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> + mIpv6ForwardingRules = new LinkedHashMap<>(); + + // Runnable that used by scheduling next polling of stats. + private final Runnable mScheduledPollingTask = () -> { + updateForwardedStatsFromNetd(); + maybeSchedulePollingStats(); + }; + + @VisibleForTesting + public static class Dependencies { + int getPerformPollInterval() { + // TODO: Consider make this configurable. + return DEFAULT_PERFORM_POLL_INTERVAL_MS; + } + } + + @VisibleForTesting + public BpfCoordinator(@NonNull Handler handler, @NonNull INetd netd, + @NonNull NetworkStatsManager nsm, @NonNull SharedLog log, @NonNull Dependencies deps) { + mHandler = handler; + mNetd = netd; + mLog = log.forSubComponent(TAG); + BpfTetherStatsProvider provider = new BpfTetherStatsProvider(); + try { + nsm.registerNetworkStatsProvider(getClass().getSimpleName(), provider); + } catch (RuntimeException e) { + // TODO: Perhaps not allow to use BPF offload because the reregistration failure + // implied that no data limit could be applies on a metered upstream if any. + Log.wtf(TAG, "Cannot register offload stats provider: " + e); + provider = null; + } + mStatsProvider = provider; + mDeps = deps; + } + + /** + * Start BPF tethering offload stats polling when the first upstream is started. + * Note that this can be only called on handler thread. + * TODO: Perhaps check BPF support before starting. + * TODO: Start the stats polling only if there is any client on the downstream. + */ + public void startPolling() { + if (mPollingStarted) return; + + mPollingStarted = true; + maybeSchedulePollingStats(); + + mLog.i("Polling started"); + } + + /** + * Stop BPF tethering offload stats polling. + * The data limit cleanup and the tether stats maps cleanup are not implemented here. + * These cleanups rely on all IpServers calling #tetherOffloadRuleRemove. After the + * last rule is removed from the upstream, #tetherOffloadRuleRemove does the cleanup + * functionality. + * Note that this can be only called on handler thread. + */ + public void stopPolling() { + if (!mPollingStarted) return; + + // Stop scheduled polling tasks and poll the latest stats from BPF maps. + if (mHandler.hasCallbacks(mScheduledPollingTask)) { + mHandler.removeCallbacks(mScheduledPollingTask); + } + updateForwardedStatsFromNetd(); + mPollingStarted = false; + + mLog.i("Polling stopped"); + } + + /** + * Add forwarding rule. After adding the first rule on a given upstream, must add the data + * limit on the given upstream. + * Note that this can be only called on handler thread. + */ + public void tetherOffloadRuleAdd( + @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { + try { + // TODO: Perhaps avoid to add a duplicate rule. + mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel()); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Could not add IPv6 forwarding rule: ", e); + return; + } + + if (!mIpv6ForwardingRules.containsKey(ipServer)) { + mIpv6ForwardingRules.put(ipServer, new LinkedHashMap<Inet6Address, + Ipv6ForwardingRule>()); + } + LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer); + + // Setup the data limit on the given upstream if the first rule is added. + final int upstreamIfindex = rule.upstreamIfindex; + if (!isAnyRuleOnUpstream(upstreamIfindex)) { + // If failed to set a data limit, probably should not use this upstream, because + // the upstream may not want to blow through the data limit that was told to apply. + // TODO: Perhaps stop the coordinator. + boolean success = updateDataLimit(upstreamIfindex); + if (!success) { + final String iface = mInterfaceNames.get(upstreamIfindex); + mLog.e("Setting data limit for " + iface + " failed."); + } + } + + // Must update the adding rule after calling #isAnyRuleOnUpstream because it needs to + // check if it is about adding a first rule for a given upstream. + rules.put(rule.address, rule); + } + + /** + * Remove forwarding rule. After removing the last rule on a given upstream, must clear + * data limit, update the last tether stats and remove the tether stats in the BPF maps. + * Note that this can be only called on handler thread. + */ + public void tetherOffloadRuleRemove( + @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { + try { + // TODO: Perhaps avoid to remove a non-existent rule. + mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel()); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Could not remove IPv6 forwarding rule: ", e); + return; + } + + LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer); + if (rules == null) return; + + // Must remove rules before calling #isAnyRuleOnUpstream because it needs to check if + // the last rule is removed for a given upstream. If no rule is removed, return early. + // Avoid unnecessary work on a non-existent rule which may have never been added or + // removed already. + if (rules.remove(rule.address) == null) return; + + // Remove the downstream entry if it has no more rule. + if (rules.isEmpty()) { + mIpv6ForwardingRules.remove(ipServer); + } + + // Do cleanup functionality if there is no more rule on the given upstream. + final int upstreamIfindex = rule.upstreamIfindex; + if (!isAnyRuleOnUpstream(upstreamIfindex)) { + try { + final TetherStatsParcel stats = + mNetd.tetherOffloadGetAndClearStats(upstreamIfindex); + // Update the last stats delta and delete the local cache for a given upstream. + updateQuotaAndStatsFromSnapshot(new TetherStatsParcel[] {stats}); + mStats.remove(upstreamIfindex); + } catch (RemoteException | ServiceSpecificException e) { + Log.wtf(TAG, "Exception when cleanup tether stats for upstream index " + + upstreamIfindex + ": ", e); + } + } + } + + /** + * Clear all forwarding rules for a given downstream. + * Note that this can be only called on handler thread. + */ + public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) { + final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get( + ipServer); + if (rules == null) return; + + // Need to build a rule list because the rule map may be changed in the iteration. + for (final Ipv6ForwardingRule rule : new ArrayList<Ipv6ForwardingRule>(rules.values())) { + tetherOffloadRuleRemove(ipServer, rule); + } + } + + /** + * Update existing forwarding rules to new upstream for a given downstream. + * Note that this can be only called on handler thread. + */ + public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) { + final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get( + ipServer); + if (rules == null) return; + + // Need to build a rule list because the rule map may be changed in the iteration. + for (final Ipv6ForwardingRule rule : new ArrayList<Ipv6ForwardingRule>(rules.values())) { + // Remove the old rule before adding the new one because the map uses the same key for + // both rules. Reversing the processing order causes that the new rule is removed as + // unexpected. + // TODO: Add new rule first to reduce the latency which has no rule. + tetherOffloadRuleRemove(ipServer, rule); + tetherOffloadRuleAdd(ipServer, rule.onNewUpstream(newUpstreamIfindex)); + } + } + + /** + * Add upstream name to lookup table. The lookup table is used for tether stats interface name + * lookup because the netd only reports interface index in BPF tether stats but the service + * expects the interface name in NetworkStats object. + * Note that this can be only called on handler thread. + */ + public void addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface) { + if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return; + + // The same interface index to name mapping may be added by different IpServer objects or + // re-added by reconnection on the same upstream interface. Ignore the duplicate one. + final String iface = mInterfaceNames.get(upstreamIfindex); + if (iface == null) { + mInterfaceNames.put(upstreamIfindex, upstreamIface); + } else if (!TextUtils.equals(iface, upstreamIface)) { + Log.wtf(TAG, "The upstream interface name " + upstreamIface + + " is different from the existing interface name " + + iface + " for index " + upstreamIfindex); + } + } + + /** IPv6 forwarding rule class. */ + public static class Ipv6ForwardingRule { + public final int upstreamIfindex; + public final int downstreamIfindex; + + @NonNull + public final Inet6Address address; + @NonNull + public final MacAddress srcMac; + @NonNull + public final MacAddress dstMac; + + public Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, + @NonNull Inet6Address address, @NonNull MacAddress srcMac, + @NonNull MacAddress dstMac) { + this.upstreamIfindex = upstreamIfindex; + this.downstreamIfindex = downstreamIfIndex; + this.address = address; + this.srcMac = srcMac; + this.dstMac = dstMac; + } + + /** Return a new rule object which updates with new upstream index. */ + @NonNull + public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) { + return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac, + dstMac); + } + + /** + * Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream() + * would be error-prone due to generated stable AIDL classes not having a copy constructor. + */ + @NonNull + public TetherOffloadRuleParcel toTetherOffloadRuleParcel() { + final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel(); + parcel.inputInterfaceIndex = upstreamIfindex; + parcel.outputInterfaceIndex = downstreamIfindex; + parcel.destination = address.getAddress(); + parcel.prefixLength = 128; + parcel.srcL2Address = srcMac.toByteArray(); + parcel.dstL2Address = dstMac.toByteArray(); + return parcel; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Ipv6ForwardingRule)) return false; + Ipv6ForwardingRule that = (Ipv6ForwardingRule) o; + return this.upstreamIfindex == that.upstreamIfindex + && this.downstreamIfindex == that.downstreamIfindex + && Objects.equals(this.address, that.address) + && Objects.equals(this.srcMac, that.srcMac) + && Objects.equals(this.dstMac, that.dstMac); + } + + @Override + public int hashCode() { + // TODO: if this is ever used in production code, don't pass ifindices + // to Objects.hash() to avoid autoboxing overhead. + return Objects.hash(upstreamIfindex, downstreamIfindex, address, srcMac, dstMac); + } + } + + /** + * A BPF tethering stats provider to provide network statistics to the system. + * Note that this class' data may only be accessed on the handler thread. + */ + @VisibleForTesting + class BpfTetherStatsProvider extends NetworkStatsProvider { + // The offloaded traffic statistics per interface that has not been reported since the + // last call to pushTetherStats. Only the interfaces that were ever tethering upstreams + // and has pending tether stats delta are included in this NetworkStats object. + private NetworkStats mIfaceStats = new NetworkStats(0L, 0); + + // The same stats as above, but counts network stats per uid. + private NetworkStats mUidStats = new NetworkStats(0L, 0); + + @Override + public void onRequestStatsUpdate(int token) { + mHandler.post(() -> pushTetherStats()); + } + + @Override + public void onSetAlert(long quotaBytes) { + mHandler.post(() -> updateAlertQuota(quotaBytes)); + } + + @Override + public void onSetLimit(@NonNull String iface, long quotaBytes) { + if (quotaBytes < QUOTA_UNLIMITED) { + throw new IllegalArgumentException("invalid quota value " + quotaBytes); + } + + mHandler.post(() -> { + final Long curIfaceQuota = mInterfaceQuotas.get(iface); + + if (null == curIfaceQuota && QUOTA_UNLIMITED == quotaBytes) return; + + if (quotaBytes == QUOTA_UNLIMITED) { + mInterfaceQuotas.remove(iface); + } else { + mInterfaceQuotas.put(iface, quotaBytes); + } + maybeUpdateDataLimit(iface); + }); + } + + @VisibleForTesting + void pushTetherStats() { + try { + // The token is not used for now. See b/153606961. + notifyStatsUpdated(0 /* token */, mIfaceStats, mUidStats); + + // Clear the accumulated tether stats delta after reported. Note that create a new + // empty object because NetworkStats#clear is @hide. + mIfaceStats = new NetworkStats(0L, 0); + mUidStats = new NetworkStats(0L, 0); + } catch (RuntimeException e) { + mLog.e("Cannot report network stats: ", e); + } + } + + private void accumulateDiff(@NonNull NetworkStats ifaceDiff, + @NonNull NetworkStats uidDiff) { + mIfaceStats = mIfaceStats.add(ifaceDiff); + mUidStats = mUidStats.add(uidDiff); + } + } + + private int getInterfaceIndexFromRules(@NonNull String ifName) { + for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules + .values()) { + for (Ipv6ForwardingRule rule : rules.values()) { + final int upstreamIfindex = rule.upstreamIfindex; + if (TextUtils.equals(ifName, mInterfaceNames.get(upstreamIfindex))) { + return upstreamIfindex; + } + } + } + return 0; + } + + private long getQuotaBytes(@NonNull String iface) { + final Long limit = mInterfaceQuotas.get(iface); + final long quotaBytes = (limit != null) ? limit : QUOTA_UNLIMITED; + + return quotaBytes; + } + + private boolean sendDataLimitToNetd(int ifIndex, long quotaBytes) { + if (ifIndex == 0) { + Log.wtf(TAG, "Invalid interface index."); + return false; + } + + try { + mNetd.tetherOffloadSetInterfaceQuota(ifIndex, quotaBytes); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Exception when updating quota " + quotaBytes + ": ", e); + return false; + } + + return true; + } + + // Handle the data limit update from the service which is the stats provider registered for. + private void maybeUpdateDataLimit(@NonNull String iface) { + // Set data limit only on a given upstream which has at least one rule. If we can't get + // an interface index for a given interface name, it means either there is no rule for + // a given upstream or the interface name is not an upstream which is monitored by the + // coordinator. + final int ifIndex = getInterfaceIndexFromRules(iface); + if (ifIndex == 0) return; + + final long quotaBytes = getQuotaBytes(iface); + sendDataLimitToNetd(ifIndex, quotaBytes); + } + + // Handle the data limit update while adding forwarding rules. + private boolean updateDataLimit(int ifIndex) { + final String iface = mInterfaceNames.get(ifIndex); + if (iface == null) { + mLog.e("Fail to get the interface name for index " + ifIndex); + return false; + } + final long quotaBytes = getQuotaBytes(iface); + return sendDataLimitToNetd(ifIndex, quotaBytes); + } + + private boolean isAnyRuleOnUpstream(int upstreamIfindex) { + for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules + .values()) { + for (Ipv6ForwardingRule rule : rules.values()) { + if (upstreamIfindex == rule.upstreamIfindex) return true; + } + } + return false; + } + + @NonNull + private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex, + @NonNull final ForwardedStats diff) { + NetworkStats stats = new NetworkStats(0L, 0); + final String iface = mInterfaceNames.get(ifIndex); + if (iface == null) { + // TODO: Use Log.wtf once the coordinator owns full control of tether stats from netd. + // For now, netd may add the empty stats for the upstream which is not monitored by + // the coordinator. Silently ignore it. + return stats; + } + final int uid = (type == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL; + // Note that the argument 'metered', 'roaming' and 'defaultNetwork' are not recorded for + // network stats snapshot. See NetworkStatsRecorder#recordSnapshotLocked. + return stats.addEntry(new Entry(iface, uid, SET_DEFAULT, TAG_NONE, METERED_NO, + ROAMING_NO, DEFAULT_NETWORK_NO, diff.rxBytes, diff.rxPackets, + diff.txBytes, diff.txPackets, 0L /* operations */)); + } + + private void updateAlertQuota(long newQuota) { + if (newQuota < QUOTA_UNLIMITED) { + throw new IllegalArgumentException("invalid quota value " + newQuota); + } + if (mRemainingAlertQuota == newQuota) return; + + mRemainingAlertQuota = newQuota; + if (mRemainingAlertQuota == 0) { + mLog.i("onAlertReached"); + if (mStatsProvider != null) mStatsProvider.notifyAlertReached(); + } + } + + private void updateQuotaAndStatsFromSnapshot( + @NonNull final TetherStatsParcel[] tetherStatsList) { + long usedAlertQuota = 0; + for (TetherStatsParcel tetherStats : tetherStatsList) { + final Integer ifIndex = tetherStats.ifIndex; + final ForwardedStats curr = new ForwardedStats(tetherStats); + final ForwardedStats base = mStats.get(ifIndex); + final ForwardedStats diff = (base != null) ? curr.subtract(base) : curr; + usedAlertQuota += diff.rxBytes + diff.txBytes; + + // Update the local cache for counting tether stats delta. + mStats.put(ifIndex, curr); + + // Update the accumulated tether stats delta to the stats provider for the service + // querying. + if (mStatsProvider != null) { + try { + mStatsProvider.accumulateDiff( + buildNetworkStats(StatsType.STATS_PER_IFACE, ifIndex, diff), + buildNetworkStats(StatsType.STATS_PER_UID, ifIndex, diff)); + } catch (ArrayIndexOutOfBoundsException e) { + Log.wtf(TAG, "Fail to update the accumulated stats delta for interface index " + + ifIndex + " : ", e); + } + } + } + + if (mRemainingAlertQuota > 0 && usedAlertQuota > 0) { + // Trim to zero if overshoot. + final long newQuota = Math.max(mRemainingAlertQuota - usedAlertQuota, 0); + updateAlertQuota(newQuota); + } + + // TODO: Count the used limit quota for notifying data limit reached. + } + + private void updateForwardedStatsFromNetd() { + final TetherStatsParcel[] tetherStatsList; + try { + // The reported tether stats are total data usage for all currently-active upstream + // interfaces since tethering start. + tetherStatsList = mNetd.tetherOffloadGetStats(); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Problem fetching tethering stats: ", e); + return; + } + updateQuotaAndStatsFromSnapshot(tetherStatsList); + } + + private void maybeSchedulePollingStats() { + if (!mPollingStarted) return; + + if (mHandler.hasCallbacks(mScheduledPollingTask)) { + mHandler.removeCallbacks(mScheduledPollingTask); + } + + mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval()); + } +} diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java index 69eec8df9864..df6745855067 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -232,6 +232,7 @@ public class Tethering { private final TetheringThreadExecutor mExecutor; private final TetheringNotificationUpdater mNotificationUpdater; private final UserManager mUserManager; + private final BpfCoordinator mBpfCoordinator; private final PrivateAddressCoordinator mPrivateAddressCoordinator; private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID; // All the usage of mTetheringEventCallback should run in the same thread. @@ -284,6 +285,8 @@ public class Tethering { mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); mForwardedDownstreams = new LinkedHashSet<>(); + mBpfCoordinator = mDeps.getBpfCoordinator( + mHandler, mNetd, mLog, new BpfCoordinator.Dependencies()); IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); @@ -1704,6 +1707,9 @@ public class Tethering { chooseUpstreamType(true); mTryCell = false; } + + // TODO: Check the upstream interface if it is managed by BPF offload. + mBpfCoordinator.startPolling(); } @Override @@ -1716,6 +1722,7 @@ public class Tethering { mTetherUpstream = null; reportUpstreamChanged(null); } + mBpfCoordinator.stopPolling(); } private boolean updateUpstreamWanted() { @@ -2341,7 +2348,7 @@ public class Tethering { mLog.log("adding TetheringInterfaceStateMachine for: " + iface); final TetherState tetherState = new TetherState( - new IpServer(iface, mLooper, interfaceType, mLog, mNetd, + new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator, makeControlCallback(), mConfig.enableLegacyDhcpServer, mConfig.enableBpfOffload, mPrivateAddressCoordinator, mDeps.getIpServerDependencies())); diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java index ce546c701a61..d637c8646b4a 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java @@ -41,6 +41,17 @@ import java.util.ArrayList; */ public abstract class TetheringDependencies { /** + * Get a reference to the BpfCoordinator to be used by tethering. + */ + public @NonNull BpfCoordinator getBpfCoordinator( + @NonNull Handler handler, @NonNull INetd netd, @NonNull SharedLog log, + @NonNull BpfCoordinator.Dependencies deps) { + final NetworkStatsManager statsManager = + (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE); + return new BpfCoordinator(handler, netd, statsManager, log, deps); + } + + /** * Get a reference to the offload hardware interface to be used by tethering. */ public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) { diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java index d03deda37fdf..593d04a06b93 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java @@ -24,8 +24,10 @@ import android.app.Notification.Action; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.net.NetworkCapabilities; @@ -253,6 +255,14 @@ public class TetheringNotificationUpdater { } @VisibleForTesting + static String getSettingsPackageName(@NonNull final PackageManager pm) { + final Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS); + final ComponentName settingsComponent = settingsIntent.resolveActivity(pm); + return settingsComponent != null + ? settingsComponent.getPackageName() : "com.android.settings"; + } + + @VisibleForTesting void notifyTetheringDisabledByRestriction() { final Resources res = getResourcesForSubId(mContext, mActiveDataSubId); final String title = res.getString(R.string.disable_tether_notification_title); @@ -262,8 +272,9 @@ public class TetheringNotificationUpdater { final PendingIntent pi = PendingIntent.getActivity( mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), 0 /* requestCode */, - new Intent(Settings.ACTION_TETHER_SETTINGS), - Intent.FLAG_ACTIVITY_NEW_TASK, + new Intent(Settings.ACTION_TETHER_SETTINGS) + .setPackage(getSettingsPackageName(mContext.getPackageManager())), + Intent.FLAG_ACTIVITY_NEW_TASK | PendingIntent.FLAG_IMMUTABLE, null /* options */); showNotification(R.drawable.stat_sys_tether_general, title, message, @@ -284,7 +295,7 @@ public class TetheringNotificationUpdater { mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), 0 /* requestCode */, intent, - 0 /* flags */); + PendingIntent.FLAG_IMMUTABLE); final Action action = new Action.Builder(NO_ICON_ID, disableButton, pi).build(); showNotification(R.drawable.stat_sys_tether_general, title, message, @@ -305,8 +316,9 @@ public class TetheringNotificationUpdater { final PendingIntent pi = PendingIntent.getActivity( mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */), 0 /* requestCode */, - new Intent(Settings.ACTION_TETHER_SETTINGS), - Intent.FLAG_ACTIVITY_NEW_TASK, + new Intent(Settings.ACTION_TETHER_SETTINGS) + .setPackage(getSettingsPackageName(mContext.getPackageManager())), + Intent.FLAG_ACTIVITY_NEW_TASK | PendingIntent.FLAG_IMMUTABLE, null /* options */); showNotification(R.drawable.stat_sys_tether_general, title, message, diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 0cda29a32f59..c3bc915a232d 100644 --- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -54,12 +54,14 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.app.usage.NetworkStatsManager; import android.net.INetd; import android.net.InetAddresses; import android.net.InterfaceConfigurationParcel; @@ -69,6 +71,7 @@ import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; import android.net.TetherOffloadRuleParcel; +import android.net.TetherStatsParcel; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpEventCallbacks; import android.net.dhcp.IDhcpServer; @@ -80,13 +83,17 @@ import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; import android.net.util.PrefixUtils; import android.net.util.SharedLog; +import android.os.Handler; import android.os.RemoteException; import android.os.test.TestLooper; import android.text.TextUtils; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.networkstack.tethering.BpfCoordinator; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.PrivateAddressCoordinator; import org.junit.Before; @@ -100,6 +107,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.util.Arrays; import java.util.List; @@ -133,6 +141,7 @@ public class IpServerTest { @Mock private IpNeighborMonitor mIpNeighborMonitor; @Mock private IpServer.Dependencies mDependencies; @Mock private PrivateAddressCoordinator mAddressCoordinator; + @Mock private NetworkStatsManager mStatsManager; @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor; @@ -142,6 +151,7 @@ public class IpServerTest { private IpServer mIpServer; private InterfaceConfigurationParcel mInterfaceConfiguration; private NeighborEventConsumer mNeighborEventConsumer; + private BpfCoordinator mBpfCoordinator; private void initStateMachine(int interfaceType) throws Exception { initStateMachine(interfaceType, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD); @@ -179,7 +189,7 @@ public class IpServerTest { neighborCaptor.capture()); mIpServer = new IpServer( - IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, + IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator, mCallback, usingLegacyDhcp, usingBpfOffload, mAddressCoordinator, mDependencies); mIpServer.start(); mNeighborEventConsumer = neighborCaptor.getValue(); @@ -215,6 +225,10 @@ public class IpServerTest { MockitoAnnotations.initMocks(this); when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog); when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(mTestAddress); + + BpfCoordinator bc = new BpfCoordinator(new Handler(mLooper.getLooper()), mNetd, + mStatsManager, mSharedLog, new BpfCoordinator.Dependencies()); + mBpfCoordinator = spy(bc); } @Test @@ -222,8 +236,8 @@ public class IpServerTest { when(mDependencies.getIpNeighborMonitor(any(), any(), any())) .thenReturn(mIpNeighborMonitor); mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog, - mNetd, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD, - mAddressCoordinator, mDependencies); + mNetd, mBpfCoordinator, mCallback, false /* usingLegacyDhcp */, + DEFAULT_USING_BPF_OFFLOAD, mAddressCoordinator, mDependencies); mIpServer.start(); mLooper.dispatchAll(); verify(mCallback).updateInterfaceState( @@ -619,6 +633,10 @@ public class IpServerTest { * (actual: "android.net.TetherOffloadRuleParcel@8c827b0" or some such), but at least it does * work. * + * TODO: consider making the error message more readable by adding a method that catching the + * AssertionFailedError and throwing a new assertion with more details. See + * NetworkMonitorTest#verifyNetworkTested. + * * See ConnectivityServiceTest#assertRoutesAdded for an alternative approach which solves the * TooManyActualInvocations problem described above by forcing the caller of the custom assert * method to specify all expected invocations in one call. This is useful when the stable @@ -658,6 +676,27 @@ public class IpServerTest { return argThat(new TetherOffloadRuleParcelMatcher(upstreamIfindex, dst, dstMac)); } + private static Ipv6ForwardingRule makeForwardingRule( + int upstreamIfindex, @NonNull InetAddress dst, @NonNull MacAddress dstMac) { + return new Ipv6ForwardingRule(upstreamIfindex, TEST_IFACE_PARAMS.index, + (Inet6Address) dst, TEST_IFACE_PARAMS.macAddr, dstMac); + } + + private TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) { + TetherStatsParcel parcel = new TetherStatsParcel(); + parcel.ifIndex = ifIndex; + return parcel; + } + + private void resetNetdAndBpfCoordinator() throws Exception { + reset(mNetd, mBpfCoordinator); + when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); + when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX)) + .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX)); + when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX2)) + .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX2)); + } + @Test public void addRemoveipv6ForwardingRules() throws Exception { initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, @@ -675,75 +714,100 @@ public class IpServerTest { final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a"); final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b"); - reset(mNetd); + resetNetdAndBpfCoordinator(); + verifyNoMoreInteractions(mBpfCoordinator, mNetd); + + // TODO: Perhaps verify the interaction of tetherOffloadSetInterfaceQuota and + // tetherOffloadGetAndClearStats in netd while the rules are changed. // Events on other interfaces are ignored. recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mNetd); + verifyNoMoreInteractions(mBpfCoordinator, mNetd); // Events on this interface are received and sent to netd. recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); + verify(mBpfCoordinator).tetherOffloadRuleAdd( + mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA)); - reset(mNetd); + resetNetdAndBpfCoordinator(); recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); + verify(mBpfCoordinator).tetherOffloadRuleAdd( + mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB)); - reset(mNetd); + resetNetdAndBpfCoordinator(); // Link-local and multicast neighbors are ignored. recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mNetd); + verifyNoMoreInteractions(mBpfCoordinator, mNetd); recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mNetd); + verifyNoMoreInteractions(mBpfCoordinator, mNetd); // A neighbor that is no longer valid causes the rule to be removed. // NUD_FAILED events do not have a MAC address. recvNewNeigh(myIfindex, neighA, NUD_FAILED, null); + verify(mBpfCoordinator).tetherOffloadRuleRemove( + mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macNull)); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macNull)); - reset(mNetd); + resetNetdAndBpfCoordinator(); // A neighbor that is deleted causes the rule to be removed. recvDelNeigh(myIfindex, neighB, NUD_STALE, macB); + verify(mBpfCoordinator).tetherOffloadRuleRemove( + mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macNull)); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macNull)); - reset(mNetd); + resetNetdAndBpfCoordinator(); - // Upstream changes result in deleting and re-adding the rules. + // Upstream changes result in updating the rules. recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - reset(mNetd); + resetNetdAndBpfCoordinator(); InOrder inOrder = inOrder(mNetd); LinkProperties lp = new LinkProperties(); lp.setInterfaceName(UPSTREAM_IFACE2); dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighA, macA)); + verify(mBpfCoordinator).tetherOffloadRuleUpdate(mIpServer, UPSTREAM_IFINDEX2); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA)); - inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighB, macB)); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighA, macA)); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB)); - reset(mNetd); + inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighB, macB)); + resetNetdAndBpfCoordinator(); // When the upstream is lost, rules are removed. dispatchTetherConnectionChanged(null, null, 0); + // Clear function is called two times by: + // - processMessage CMD_TETHER_CONNECTION_CHANGED for the upstream is lost. + // - processMessage CMD_IPV6_TETHER_UPDATE for the IPv6 upstream is lost. + // See dispatchTetherConnectionChanged. + verify(mBpfCoordinator, times(2)).tetherOffloadRuleClear(mIpServer); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighA, macA)); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighB, macB)); - reset(mNetd); + resetNetdAndBpfCoordinator(); // If the upstream is IPv4-only, no rules are added. dispatchTetherConnectionChanged(UPSTREAM_IFACE); - reset(mNetd); + resetNetdAndBpfCoordinator(); recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mNetd); + // Clear function is called by #updateIpv6ForwardingRules for the IPv6 upstream is lost. + verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); + verifyNoMoreInteractions(mBpfCoordinator, mNetd); // Rules can be added again once upstream IPv6 connectivity is available. lp.setInterfaceName(UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1); recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); + verify(mBpfCoordinator).tetherOffloadRuleAdd( + mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB)); + verify(mBpfCoordinator, never()).tetherOffloadRuleAdd( + mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); verify(mNetd, never()).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA)); // If upstream IPv6 connectivity is lost, rules are removed. - reset(mNetd); + resetNetdAndBpfCoordinator(); dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0); + verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB)); // When the interface goes down, rules are removed. @@ -751,15 +815,20 @@ public class IpServerTest { dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1); recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); + verify(mBpfCoordinator).tetherOffloadRuleAdd( + mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA)); + verify(mBpfCoordinator).tetherOffloadRuleAdd( + mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB)); - reset(mNetd); + resetNetdAndBpfCoordinator(); mIpServer.stop(); mLooper.dispatchAll(); + verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA)); verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB)); - reset(mNetd); + resetNetdAndBpfCoordinator(); } @Test @@ -769,35 +838,46 @@ public class IpServerTest { final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a"); final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00"); - reset(mNetd); - // Expect that rules can be only added/removed when the BPF offload config is enabled. - // Note that the usingBpfOffload false case is not a realistic test case. Because IP + // Note that the BPF offload disabled case is not a realistic test case. Because IP // neighbor monitor doesn't start if BPF offload is disabled, there should have no // neighbor event listening. This is used for testing the protection check just in case. - // TODO: Perhaps remove this test once we don't need this check anymore. - for (boolean usingBpfOffload : new boolean[]{true, false}) { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, - usingBpfOffload); - - // A neighbor is added. - recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); - if (usingBpfOffload) { - verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neigh, macA)); - } else { - verify(mNetd, never()).tetherOffloadRuleAdd(any()); - } - reset(mNetd); - - // A neighbor is deleted. - recvDelNeigh(myIfindex, neigh, NUD_STALE, macA); - if (usingBpfOffload) { - verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neigh, macNull)); - } else { - verify(mNetd, never()).tetherOffloadRuleRemove(any()); - } - reset(mNetd); - } + // TODO: Perhaps remove the BPF offload disabled case test once this check isn't needed + // anymore. + + // [1] Enable BPF offload. + // A neighbor that is added or deleted causes the rule to be added or removed. + initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, + true /* usingBpfOffload */); + resetNetdAndBpfCoordinator(); + + recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); + verify(mBpfCoordinator).tetherOffloadRuleAdd( + mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macA)); + verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neigh, macA)); + resetNetdAndBpfCoordinator(); + + recvDelNeigh(myIfindex, neigh, NUD_STALE, macA); + verify(mBpfCoordinator).tetherOffloadRuleRemove( + mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macNull)); + verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neigh, macNull)); + resetNetdAndBpfCoordinator(); + + // [2] Disable BPF offload. + // A neighbor that is added or deleted doesn’t cause the rule to be added or removed. + initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, + false /* usingBpfOffload */); + resetNetdAndBpfCoordinator(); + + recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); + verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(any(), any()); + verify(mNetd, never()).tetherOffloadRuleAdd(any()); + resetNetdAndBpfCoordinator(); + + recvDelNeigh(myIfindex, neigh, NUD_STALE, macA); + verify(mBpfCoordinator, never()).tetherOffloadRuleRemove(any(), any()); + verify(mNetd, never()).tetherOffloadRuleRemove(any()); + resetNetdAndBpfCoordinator(); } @Test diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java new file mode 100644 index 000000000000..e2d7aab4e33f --- /dev/null +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -0,0 +1,246 @@ +/* + * 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.networkstack.tethering; + +import static android.net.NetworkStats.DEFAULT_NETWORK_NO; +import static android.net.NetworkStats.METERED_NO; +import static android.net.NetworkStats.ROAMING_NO; +import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.TAG_NONE; +import static android.net.NetworkStats.UID_ALL; +import static android.net.NetworkStats.UID_TETHERING; +import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; + +import static com.android.networkstack.tethering.BpfCoordinator + .DEFAULT_PERFORM_POLL_INTERVAL_MS; +import static com.android.networkstack.tethering.BpfCoordinator.StatsType; +import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE; +import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID; + +import static junit.framework.Assert.assertNotNull; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.app.usage.NetworkStatsManager; +import android.net.INetd; +import android.net.NetworkStats; +import android.net.TetherStatsParcel; +import android.net.util.SharedLog; +import android.os.Handler; +import android.os.test.TestLooper; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.testutils.TestableNetworkStatsProviderCbBinder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class BpfCoordinatorTest { + @Mock private NetworkStatsManager mStatsManager; + @Mock private INetd mNetd; + // Late init since methods must be called by the thread that created this object. + private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb; + private BpfCoordinator.BpfTetherStatsProvider mTetherStatsProvider; + private final ArgumentCaptor<ArrayList> mStringArrayCaptor = + ArgumentCaptor.forClass(ArrayList.class); + private final TestLooper mTestLooper = new TestLooper(); + private BpfCoordinator.Dependencies mDeps = + new BpfCoordinator.Dependencies() { + @Override + int getPerformPollInterval() { + return DEFAULT_PERFORM_POLL_INTERVAL_MS; + } + }; + + @Before public void setUp() { + MockitoAnnotations.initMocks(this); + } + + private void waitForIdle() { + mTestLooper.dispatchAll(); + } + + private void setupFunctioningNetdInterface() throws Exception { + when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); + } + + @NonNull + private BpfCoordinator makeBpfCoordinator() throws Exception { + BpfCoordinator coordinator = new BpfCoordinator( + new Handler(mTestLooper.getLooper()), mNetd, mStatsManager, new SharedLog("test"), + mDeps); + final ArgumentCaptor<BpfCoordinator.BpfTetherStatsProvider> + tetherStatsProviderCaptor = + ArgumentCaptor.forClass(BpfCoordinator.BpfTetherStatsProvider.class); + verify(mStatsManager).registerNetworkStatsProvider(anyString(), + tetherStatsProviderCaptor.capture()); + mTetherStatsProvider = tetherStatsProviderCaptor.getValue(); + assertNotNull(mTetherStatsProvider); + mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder(); + mTetherStatsProvider.setProviderCallbackBinder(mTetherStatsProviderCb); + return coordinator; + } + + @NonNull + private static NetworkStats.Entry buildTestEntry(@NonNull StatsType how, + @NonNull String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) { + return new NetworkStats.Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING, + SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, + rxPackets, txBytes, txPackets, 0L); + } + + @NonNull + private static TetherStatsParcel buildTestTetherStatsParcel(@NonNull Integer ifIndex, + long rxBytes, long rxPackets, long txBytes, long txPackets) { + final TetherStatsParcel parcel = new TetherStatsParcel(); + parcel.ifIndex = ifIndex; + parcel.rxBytes = rxBytes; + parcel.rxPackets = rxPackets; + parcel.txBytes = txBytes; + parcel.txPackets = txPackets; + return parcel; + } + + private void setTetherOffloadStatsList(TetherStatsParcel[] tetherStatsList) throws Exception { + when(mNetd.tetherOffloadGetStats()).thenReturn(tetherStatsList); + mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + waitForIdle(); + } + + @Test + public void testGetForwardedStats() throws Exception { + setupFunctioningNetdInterface(); + + final BpfCoordinator coordinator = makeBpfCoordinator(); + coordinator.startPolling(); + + final String wlanIface = "wlan0"; + final Integer wlanIfIndex = 100; + final String mobileIface = "rmnet_data0"; + final Integer mobileIfIndex = 101; + + // Add interface name to lookup table. In realistic case, the upstream interface name will + // be added by IpServer when IpServer has received with a new IPv6 upstream update event. + coordinator.addUpstreamNameToLookupTable(wlanIfIndex, wlanIface); + coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + + // [1] Both interface stats are changed. + // Setup the tether stats of wlan and mobile interface. Note that move forward the time of + // the looper to make sure the new tether stats has been updated by polling update thread. + setTetherOffloadStatsList(new TetherStatsParcel[] { + buildTestTetherStatsParcel(wlanIfIndex, 1000, 100, 2000, 200), + buildTestTetherStatsParcel(mobileIfIndex, 3000, 300, 4000, 400)}); + + final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2) + .addEntry(buildTestEntry(STATS_PER_IFACE, wlanIface, 1000, 100, 2000, 200)) + .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 3000, 300, 4000, 400)); + + final NetworkStats expectedUidStats = new NetworkStats(0L, 2) + .addEntry(buildTestEntry(STATS_PER_UID, wlanIface, 1000, 100, 2000, 200)) + .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 3000, 300, 4000, 400)); + + // Force pushing stats update to verify the stats reported. + // TODO: Perhaps make #expectNotifyStatsUpdated to use test TetherStatsParcel object for + // verifying the notification. + mTetherStatsProvider.pushTetherStats(); + mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStats, expectedUidStats); + + // [2] Only one interface stats is changed. + // The tether stats of mobile interface is accumulated and The tether stats of wlan + // interface is the same. + setTetherOffloadStatsList(new TetherStatsParcel[] { + buildTestTetherStatsParcel(wlanIfIndex, 1000, 100, 2000, 200), + buildTestTetherStatsParcel(mobileIfIndex, 3010, 320, 4030, 440)}); + + final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2) + .addEntry(buildTestEntry(STATS_PER_IFACE, wlanIface, 0, 0, 0, 0)) + .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 10, 20, 30, 40)); + + final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2) + .addEntry(buildTestEntry(STATS_PER_UID, wlanIface, 0, 0, 0, 0)) + .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 10, 20, 30, 40)); + + // Force pushing stats update to verify that only diff of stats is reported. + mTetherStatsProvider.pushTetherStats(); + mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStatsDiff, + expectedUidStatsDiff); + + // [3] Stop coordinator. + // Shutdown the coordinator and clear the invocation history, especially the + // tetherOffloadGetStats() calls. + coordinator.stopPolling(); + clearInvocations(mNetd); + + // Verify the polling update thread stopped. + mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + waitForIdle(); + verify(mNetd, never()).tetherOffloadGetStats(); + } + + @Test + public void testOnSetAlert() throws Exception { + setupFunctioningNetdInterface(); + + final BpfCoordinator coordinator = makeBpfCoordinator(); + coordinator.startPolling(); + + final String mobileIface = "rmnet_data0"; + final Integer mobileIfIndex = 100; + coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + + // Verify that set quota to 0 will immediately triggers a callback. + mTetherStatsProvider.onSetAlert(0); + waitForIdle(); + mTetherStatsProviderCb.expectNotifyAlertReached(); + + // Verify that notifyAlertReached never fired if quota is not yet reached. + when(mNetd.tetherOffloadGetStats()).thenReturn( + new TetherStatsParcel[] {buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0)}); + mTetherStatsProvider.onSetAlert(100); + mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + waitForIdle(); + mTetherStatsProviderCb.assertNoCallback(); + + // Verify that notifyAlertReached fired when quota is reached. + when(mNetd.tetherOffloadGetStats()).thenReturn( + new TetherStatsParcel[] {buildTestTetherStatsParcel(mobileIfIndex, 50, 0, 50, 0)}); + mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + waitForIdle(); + mTetherStatsProviderCb.expectNotifyAlertReached(); + + // Verify that set quota with UNLIMITED won't trigger any callback. + mTetherStatsProvider.onSetAlert(QUOTA_UNLIMITED); + mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS); + waitForIdle(); + mTetherStatsProviderCb.assertNoCallback(); + } +} diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt index 7d5471f7703d..4b6bbac051e0 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt @@ -19,6 +19,10 @@ package com.android.networkstack.tethering import android.app.Notification import android.app.NotificationManager import android.content.Context +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo import android.content.res.Resources import android.net.ConnectivityManager.TETHERING_WIFI import android.os.Handler @@ -51,6 +55,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.times @@ -351,4 +356,26 @@ class TetheringNotificationUpdaterTest { notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES) verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID)) } + + @Test + fun testGetSettingsPackageName() { + val defaultSettingsPackageName = "com.android.settings" + val testSettingsPackageName = "com.android.test.settings" + val pm = mock(PackageManager::class.java) + doReturn(null).`when`(pm).resolveActivity(any(), anyInt()) + assertEquals(defaultSettingsPackageName, + TetheringNotificationUpdater.getSettingsPackageName(pm)) + + val resolveInfo = ResolveInfo().apply { + activityInfo = ActivityInfo().apply { + name = "test" + applicationInfo = ApplicationInfo().apply { + packageName = testSettingsPackageName + } + } + } + doReturn(resolveInfo).`when`(pm).resolveActivity(any(), anyInt()) + assertEquals(testSettingsPackageName, + TetheringNotificationUpdater.getSettingsPackageName(pm)) + } } diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index bb65b18edb8a..8146a58dddcb 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -203,6 +203,7 @@ public class TetheringTest { @Mock private ConnectivityManager mCm; @Mock private EthernetManager mEm; @Mock private TetheringNotificationUpdater mNotificationUpdater; + @Mock private BpfCoordinator mBpfCoordinator; private final MockIpServerDependencies mIpServerDependencies = spy(new MockIpServerDependencies()); @@ -337,6 +338,12 @@ public class TetheringTest { } @Override + public BpfCoordinator getBpfCoordinator(Handler handler, INetd netd, + SharedLog log, BpfCoordinator.Dependencies deps) { + return mBpfCoordinator; + } + + @Override public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) { return mOffloadHardwareInterface; } diff --git a/services/core/Android.bp b/services/core/Android.bp index 9e8a8727dc36..65e98ac8e684 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -100,7 +100,7 @@ java_library_static { "android.net.ipsec.ike.stubs.module_lib", "app-compat-annotations", "framework-tethering.stubs.module_lib", - "service-permission-stubs", + "service-permission.stubs.system_server", ], required: [ diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index c5c3cafa8813..a7bf98280af8 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -4918,52 +4918,6 @@ public final class ActiveServices { if (isDeviceOwner) { return true; } - - r.mInfoDenyWhileInUsePermissionInFgs = - "Background FGS start while-in-use permission restriction [callingPackage: " - + callingPackage - + "; callingUid: " + callingUid - + "; intent: " + intent - + "]"; return false; } - - // TODO: remove this toast after feature development is done - void showWhileInUseDebugToastLocked(int uid, int op, int mode) { - final UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(uid); - if (uidRec == null) { - return; - } - - for (int i = uidRec.procRecords.size() - 1; i >= 0; i--) { - ProcessRecord pr = uidRec.procRecords.valueAt(i); - if (pr.uid != uid) { - continue; - } - for (int j = pr.numberOfRunningServices() - 1; j >= 0; j--) { - ServiceRecord r = pr.getRunningServiceAt(j); - if (!r.isForeground) { - continue; - } - if (mode == DEBUG_FGS_ALLOW_WHILE_IN_USE) { - if (!r.mAllowWhileInUsePermissionInFgs - && r.mInfoDenyWhileInUsePermissionInFgs != null) { - final String msg = r.mInfoDenyWhileInUsePermissionInFgs - + " affected while-in-use permission:" - + AppOpsManager.opToPublicName(op); - Slog.wtf(TAG, msg); - } - } else if (mode == DEBUG_FGS_ENFORCE_TYPE) { - final String msg = - "FGS Missing foregroundServiceType in manifest file [callingPackage: " - + r.mRecentCallingPackage - + "; intent:" + r.intent.getIntent() - + "] affected while-in-use permission:" - + AppOpsManager.opToPublicName(op) - + "; targetSdkVersion:" + r.appInfo.targetSdkVersion; - Slog.wtf(TAG, msg); - } - } - } - } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 933dc99a43f0..026f147e955c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -19671,15 +19671,6 @@ public class ActivityManagerService extends IActivityManager.Stub return false; } - // TODO: remove this toast after feature development is done - @Override - public void showWhileInUseDebugToast(int uid, int op, int mode) { - synchronized (ActivityManagerService.this) { - ActivityManagerService.this.mServices.showWhileInUseDebugToastLocked( - uid, op, mode); - } - } - @Override public void setDeviceOwnerUid(int uid) { synchronized (ActivityManagerService.this) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 03ff3d0e0978..da5f48962130 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -1508,16 +1508,7 @@ public final class OomAdjuster { capabilityFromFGS |= (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0; - } else { - //The FGS has the location capability, but due to FGS BG start restriction it - //lost the capability, use temp location capability to mark this case. - //TODO: remove this block when development is done. - capabilityFromFGS |= - (fgsType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0 - ? ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION - : 0; - } - if (s.mAllowWhileInUsePermissionInFgs) { + boolean enabled = false; try { enabled = mPlatformCompat.isChangeEnabled( @@ -1527,23 +1518,13 @@ public final class OomAdjuster { if (enabled) { capabilityFromFGS |= (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA) - != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA - : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA; + != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA : 0; capabilityFromFGS |= (fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE) - != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE - : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; + != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE : 0; } else { - // Remove fgsType check and assign PROCESS_CAPABILITY_FOREGROUND_CAMERA - // and MICROPHONE when finish debugging. - capabilityFromFGS |= - (fgsType & FOREGROUND_SERVICE_TYPE_CAMERA) - != 0 ? PROCESS_CAPABILITY_FOREGROUND_CAMERA - : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q; - capabilityFromFGS |= - (fgsType & FOREGROUND_SERVICE_TYPE_MICROPHONE) - != 0 ? PROCESS_CAPABILITY_FOREGROUND_MICROPHONE - : ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q; + capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_CAMERA + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; } } } diff --git a/services/core/java/com/android/server/am/PendingStartActivityUids.java b/services/core/java/com/android/server/am/PendingStartActivityUids.java index 0ed99fedf23d..6bf9d4e5c3f0 100644 --- a/services/core/java/com/android/server/am/PendingStartActivityUids.java +++ b/services/core/java/com/android/server/am/PendingStartActivityUids.java @@ -18,7 +18,6 @@ package com.android.server.am; import android.content.Context; import android.os.SystemClock; -import android.provider.Settings; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -58,8 +57,7 @@ final class PendingStartActivityUids { if (delay >= 1000 /*ms*/) { Slog.i(TAG, "PendingStartActivityUids startActivity to updateOomAdj delay:" - + delay + "ms," + " uid:" + uid + " packageName:" - + Settings.getPackageNameForUid(mContext, uid)); + + delay + "ms," + " uid:" + uid); } mPendingUids.delete(uid); } diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 4670d58ca63b..9c96e6e02566 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -138,10 +138,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // allow while-in-use permissions in foreground service or not. // while-in-use permissions in FGS started from background might be restricted. boolean mAllowWhileInUsePermissionInFgs; - // information string what/why service is denied while-in-use permissions when - // foreground service is started from background. - // TODO: remove this field after feature development is done - String mInfoDenyWhileInUsePermissionInFgs; + // the most recent package that start/bind this service. String mRecentCallingPackage; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 435e82535baf..73c98da6aa56 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -16,11 +16,6 @@ package com.android.server.appop; -import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA; -import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q; -import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION; -import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; -import static android.app.ActivityManager.DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_CAMERA; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION; import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; @@ -520,8 +515,6 @@ public class AppOpsService extends IAppOpsService.Stub { public SparseBooleanArray foregroundOps; public boolean hasForegroundWatchers; - public long lastTimeShowDebugToast; - public UidState(int uid) { this.uid = uid; } @@ -557,44 +550,19 @@ public class AppOpsService extends IAppOpsService.Stub { case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: if ((capability & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) { return MODE_ALLOWED; - } else if ((capability - & DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0) { - // The FGS has the location capability, but due to FGS BG start - // restriction it lost the capability, use temp location capability - // to mark this case. - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE); - return MODE_IGNORED; } else { return MODE_IGNORED; } case OP_CAMERA: if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { return MODE_ALLOWED; - } else if ((capability & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q) - != 0) { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE); - return MODE_ALLOWED; - } else if ((capability & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA) - != 0) { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE); - return MODE_IGNORED; } else { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE); return MODE_IGNORED; } case OP_RECORD_AUDIO: if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { return MODE_ALLOWED; - } else if ((capability - & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q) != 0) { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE); - return MODE_ALLOWED; - } else if ((capability - & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE); - return MODE_IGNORED; } else { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE); return MODE_IGNORED; } default: @@ -612,15 +580,7 @@ public class AppOpsService extends IAppOpsService.Stub { return MODE_ALLOWED; } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { return MODE_ALLOWED; - } else if ((capability - & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA_Q) != 0) { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE); - return MODE_ALLOWED; - } else if ((capability & DEBUG_PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE); - return MODE_IGNORED; } else { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE); return MODE_IGNORED; } case OP_RECORD_AUDIO: @@ -629,16 +589,7 @@ public class AppOpsService extends IAppOpsService.Stub { return MODE_ALLOWED; } else if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) { return MODE_ALLOWED; - } else if ((capability & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE_Q) - != 0) { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE); - return MODE_ALLOWED; - } else if ((capability & DEBUG_PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) - != 0) { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ENFORCE_TYPE); - return MODE_IGNORED; } else { - maybeShowWhileInUseDebugToast(op, DEBUG_FGS_ALLOW_WHILE_IN_USE); return MODE_IGNORED; } default: @@ -692,27 +643,6 @@ public class AppOpsService extends IAppOpsService.Stub { } foregroundOps = which; } - - // TODO: remove this toast after feature development is done - // For DEBUG_FGS_ALLOW_WHILE_IN_USE, if the procstate is foreground service and while-in-use - // permission is denied, show a toast message and generate a WTF log so we know - // how many apps are impacted by the new background started foreground service while-in-use - // permission restriction. - // For DEBUG_FGS_ENFORCE_TYPE, The process has a foreground service that does not have - // camera/microphone foregroundServiceType in manifest file, and the process is asking - // AppOps for camera/microphone ops, show a toast message and generate a WTF log. - void maybeShowWhileInUseDebugToast(int op, int mode) { - if (mode == DEBUG_FGS_ALLOW_WHILE_IN_USE && state != UID_STATE_FOREGROUND_SERVICE) { - return; - } - final long now = SystemClock.elapsedRealtime(); - if (lastTimeShowDebugToast == 0 || now - lastTimeShowDebugToast > 600000) { - lastTimeShowDebugToast = now; - mHandler.sendMessage(PooledLambda.obtainMessage( - ActivityManagerInternal::showWhileInUseDebugToast, - mActivityManagerInternal, uid, op, mode)); - } - } } final static class Ops extends SparseArray<Op> { diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java index d38ee678c7aa..7950fcf7234c 100644 --- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java +++ b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java @@ -104,8 +104,6 @@ public class ManagedProfilePasswordCache { // Generate auth-bound key to user 0 (since we the caller is user 0) .setUserAuthenticationRequired(true) .setUserAuthenticationValidityDurationSeconds(CACHE_TIMEOUT_SECONDS) - // Only accessible after user 0's keyguard is unlocked - .setUnlockedDeviceRequired(true) .build()); key = generator.generateKey(); } catch (GeneralSecurityException e) { @@ -171,10 +169,14 @@ public class ManagedProfilePasswordCache { public void removePassword(int userId) { synchronized (mEncryptedPasswords) { String keyName = getEncryptionKeyName(userId); + String legacyKeyName = getLegacyEncryptionKeyName(userId); try { if (mKeyStore.containsAlias(keyName)) { mKeyStore.deleteEntry(keyName); } + if (mKeyStore.containsAlias(legacyKeyName)) { + mKeyStore.deleteEntry(legacyKeyName); + } } catch (KeyStoreException e) { Slog.d(TAG, "Cannot delete key", e); } @@ -186,6 +188,14 @@ public class ManagedProfilePasswordCache { } private static String getEncryptionKeyName(int userId) { + return "com.android.server.locksettings.unified_profile_cache_v2_" + userId; + } + + /** + * Returns the legacy keystore key name when setUnlockedDeviceRequired() was set explicitly. + * Only existed during Android 11 internal testing period. + */ + private static String getLegacyEncryptionKeyName(int userId) { return "com.android.server.locksettings.unified_profile_cache_" + userId; } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5585e9816783..a9139adff4f9 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -92,6 +92,7 @@ import static android.service.notification.NotificationListenerService.TRIM_FULL import static android.service.notification.NotificationListenerService.TRIM_LIGHT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; +import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES; @@ -1906,7 +1907,8 @@ public class NotificationManagerService extends SystemService { mMetricsLogger = new MetricsLogger(); mRankingHandler = rankingHandler; mConditionProviders = conditionProviders; - mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders); + mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders, + new SysUiStatsEvent.BuilderFactory()); mZenModeHelper.addCallback(new ZenModeHelper.Callback() { @Override public void onConfigChanged() { @@ -2189,6 +2191,12 @@ public class NotificationManagerService extends SystemService { ConcurrentUtils.DIRECT_EXECUTOR, mPullAtomCallback ); + mStatsManager.setPullAtomCallback( + DND_MODE_RULE, + null, // use default PullAtomMetadata values + BackgroundThread.getExecutor(), + mPullAtomCallback + ); } private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback { @@ -2198,6 +2206,7 @@ public class NotificationManagerService extends SystemService { case PACKAGE_NOTIFICATION_PREFERENCES: case PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES: case PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES: + case DND_MODE_RULE: return pullNotificationStates(atomTag, data); default: throw new UnsupportedOperationException("Unknown tagId=" + atomTag); @@ -2216,6 +2225,9 @@ public class NotificationManagerService extends SystemService { case PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES: mPreferencesHelper.pullPackageChannelGroupPreferencesStats(data); break; + case DND_MODE_RULE: + mZenModeHelper.pullRules(data); + break; } return StatsManager.PULL_SUCCESS; } @@ -4015,7 +4027,7 @@ public class NotificationManagerService extends SystemService { private void cancelNotificationFromListenerLocked(ManagedServiceInfo info, int callingUid, int callingPid, String pkg, String tag, int id, int userId) { cancelNotification(callingUid, callingPid, pkg, tag, id, 0, - FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, + FLAG_ONGOING_EVENT | FLAG_FOREGROUND_SERVICE, true, userId, REASON_LISTENER_CANCEL, info); } @@ -6248,6 +6260,13 @@ public class NotificationManagerService extends SystemService { mUsageStats.registerClickedByUser(r); } + if (mReason == REASON_LISTENER_CANCEL + && (r.getNotification().flags & FLAG_BUBBLE) != 0) { + mNotificationDelegate.onBubbleNotificationSuppressionChanged( + r.getKey(), /* suppressed */ true); + return; + } + if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) { return; } diff --git a/services/core/java/com/android/server/notification/SysUiStatsEvent.java b/services/core/java/com/android/server/notification/SysUiStatsEvent.java index 9bc2346d4e96..90737bdeae4a 100644 --- a/services/core/java/com/android/server/notification/SysUiStatsEvent.java +++ b/services/core/java/com/android/server/notification/SysUiStatsEvent.java @@ -58,6 +58,11 @@ public class SysUiStatsEvent { mBuilder.writeBoolean(value); return this; } + + public Builder writeByteArray(byte[] value) { + mBuilder.writeByteArray(value); + return this; + } } static class BuilderFactory { diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index a490b9c99bb4..4931d3f3e95c 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -20,6 +20,10 @@ import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; +import static android.service.notification.DNDModeProto.ROOT_CONFIG; + +import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; +import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import android.app.AppOpsManager; import android.app.AutomaticZenRule; @@ -67,6 +71,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.util.StatsEvent; import android.util.proto.ProtoOutputStream; import com.android.internal.R; @@ -103,6 +108,7 @@ public class ZenModeHelper { private final SettingsObserver mSettingsObserver; private final AppOpsManager mAppOps; @VisibleForTesting protected final NotificationManager mNotificationManager; + private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory; @VisibleForTesting protected ZenModeConfig mDefaultConfig; private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final ZenModeFiltering mFiltering; @@ -130,7 +136,8 @@ public class ZenModeHelper { private String[] mPriorityOnlyDndExemptPackages; - public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) { + public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders, + SysUiStatsEvent.BuilderFactory statsEventBuilderFactory) { mContext = context; mHandler = new H(looper); addCallback(mMetrics); @@ -148,6 +155,7 @@ public class ZenModeHelper { mFiltering = new ZenModeFiltering(mContext); mConditions = new ZenModeConditions(this, conditionProviders); mServiceConfig = conditionProviders.getConfig(); + mStatsEventBuilderFactory = statsEventBuilderFactory; } public Looper getLooper() { @@ -1170,6 +1178,72 @@ public class ZenModeHelper { } } + /** + * Generate pulled atoms about do not disturb configurations. + */ + public void pullRules(List<StatsEvent> events) { + synchronized (mConfig) { + final int numConfigs = mConfigs.size(); + int id = 0; + for (int i = 0; i < numConfigs; i++) { + final int user = mConfigs.keyAt(i); + final ZenModeConfig config = mConfigs.valueAt(i); + SysUiStatsEvent.Builder data = mStatsEventBuilderFactory.newBuilder() + .setAtomId(DND_MODE_RULE) + .writeInt(user) + .writeBoolean(config.manualRule != null) // enabled + .writeBoolean(config.areChannelsBypassingDnd) + .writeInt(ROOT_CONFIG) + .writeString("") // name, empty for root config + .writeInt(Process.SYSTEM_UID) // system owns root config + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true) + .writeByteArray(config.toZenPolicy().toProto()); + events.add(data.build()); + if (config.manualRule != null && config.manualRule.enabler != null) { + ruleToProto(user, config.manualRule, events); + } + for (ZenRule rule : config.automaticRules.values()) { + ruleToProto(user, rule, events); + } + } + } + } + + private void ruleToProto(int user, ZenRule rule, List<StatsEvent> events) { + // Make the ID safe. + String id = rule.id == null ? "" : rule.id; + if (!ZenModeConfig.DEFAULT_RULE_IDS.contains(id)) { + id = ""; + } + + // Look for packages and enablers, enablers get priority. + String pkg = rule.pkg == null ? "" : rule.pkg; + if (rule.enabler != null) { + pkg = rule.enabler; + id = ZenModeConfig.MANUAL_RULE_ID; + } + + // TODO: fetch the uid from the package manager + int uid = "android".equals(pkg) ? Process.SYSTEM_UID : 0; + + SysUiStatsEvent.Builder data; + data = mStatsEventBuilderFactory.newBuilder() + .setAtomId(DND_MODE_RULE) + .writeInt(user) + .writeBoolean(rule.enabled) + .writeBoolean(false) // channels_bypassing unused for rules + .writeInt(rule.zenMode) + .writeString(id) + .writeInt(uid) + .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true); + byte[] policyProto = new byte[]{}; + if (rule.zenPolicy != null) { + policyProto = rule.zenPolicy.toProto(); + } + data.writeByteArray(policyProto); + events.add(data.build()); + } + @VisibleForTesting protected final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate { @Override diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 40fa798309c1..2a6997cba4bb 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -989,6 +989,15 @@ public class UserManagerService extends IUserManager.Stub { ensureCanModifyQuietMode( callingPackage, Binder.getCallingUid(), userId, target != null, dontAskCredential); + + if (onlyIfCredentialNotRequired && callingPackage.equals( + getPackageManagerInternal().getSystemUiServiceComponent().getPackageName())) { + // This is to prevent SysUI from accidentally allowing the profile to turned on + // without password when keyguard is still locked. + throw new SecurityException("SystemUI is not allowed to set " + + "QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED"); + } + final long identity = Binder.clearCallingIdentity(); try { if (enableQuietMode) { @@ -996,7 +1005,17 @@ public class UserManagerService extends IUserManager.Stub { userId, true /* enableQuietMode */, target, callingPackage); return true; } - mLockPatternUtils.tryUnlockWithCachedUnifiedChallenge(userId); + if (mLockPatternUtils.isManagedProfileWithUnifiedChallenge(userId)) { + KeyguardManager km = mContext.getSystemService(KeyguardManager.class); + // Normally only attempt to auto-unlock unified challenge if keyguard is not showing + // (to stop turning profile on automatically via the QS tile), except when we + // are called with QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED, in which + // case always attempt to auto-unlock. + if (!km.isDeviceLocked(mLocalService.getProfileParentId(userId)) + || onlyIfCredentialNotRequired) { + mLockPatternUtils.tryUnlockWithCachedUnifiedChallenge(userId); + } + } final boolean needToShowConfirmCredential = !dontAskCredential && mLockPatternUtils.isSecure(userId) && !StorageManager.isUserKeyUnlocked(userId); @@ -1029,6 +1048,8 @@ public class UserManagerService extends IUserManager.Stub { */ private void ensureCanModifyQuietMode(String callingPackage, int callingUid, @UserIdInt int targetUserId, boolean startIntent, boolean dontAskCredential) { + verifyCallingPackage(callingPackage, callingUid); + if (hasManageUsersPermission()) { return; } @@ -1050,7 +1071,6 @@ public class UserManagerService extends IUserManager.Stub { return; } - verifyCallingPackage(callingPackage, callingUid); final ShortcutServiceInternal shortcutInternal = LocalServices.getService(ShortcutServiceInternal.class); if (shortcutInternal != null) { diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java index 58732b4c778f..7c89b9850c5d 100644 --- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java +++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java @@ -96,6 +96,8 @@ import java.util.Set; class UserSystemPackageInstaller { private static final String TAG = "UserManagerService"; + private static final boolean DEBUG = false; + /** * System Property whether to only install system packages on a user if they're whitelisted for * that user type. These are flags and can be freely combined. @@ -328,12 +330,15 @@ class UserSystemPackageInstaller { // Check whether all whitelisted packages are indeed on the system. final String notPresentFmt = "%s is whitelisted but not present."; final String notSystemFmt = "%s is whitelisted and present but not a system package."; + final String overlayPackageFmt = "%s is whitelisted but it's auto-generated RRO package."; for (String pkgName : allWhitelistedPackages) { final AndroidPackage pkg = pmInt.getPackage(pkgName); if (pkg == null) { warnings.add(String.format(notPresentFmt, pkgName)); } else if (!pkg.isSystem()) { warnings.add(String.format(notSystemFmt, pkgName)); + } else if (isAutoGeneratedRRO(pkg)) { + warnings.add(String.format(overlayPackageFmt, pkgName)); } } return warnings; @@ -407,6 +412,23 @@ class UserSystemPackageInstaller { return isImplicitWhitelistSystemMode(getWhitelistMode()); } + /** + * Whether package name has auto-generated RRO package name suffix. + */ + @VisibleForTesting + static boolean hasAutoGeneratedRROSuffix(String name) { + return name.endsWith(".auto_generated_rro_product__") + || name.endsWith(".auto_generated_rro_vendor__"); + } + + /** + * Whether the package is auto-generated RRO package. + */ + private static boolean isAutoGeneratedRRO(AndroidPackage pkg) { + return pkg.isOverlay() + && (hasAutoGeneratedRROSuffix(pkg.getManifestPackageName())); + } + /** See {@link #isEnforceMode()}. */ private static boolean isEnforceMode(int whitelistMode) { return (whitelistMode & USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE) != 0; @@ -518,7 +540,18 @@ class UserSystemPackageInstaller { static boolean shouldInstallPackage(AndroidPackage sysPkg, @NonNull ArrayMap<String, Long> userTypeWhitelist, @NonNull Set<String> userWhitelist, boolean implicitlyWhitelist) { - final String pkgName = sysPkg.getManifestPackageName(); + final String pkgName; + if (isAutoGeneratedRRO(sysPkg)) { + pkgName = sysPkg.getOverlayTarget(); + if (DEBUG) { + Slog.i(TAG, "shouldInstallPackage(): " + sysPkg.getManifestPackageName() + + " is auto-generated RRO package, will look for overlay system package: " + + pkgName); + } + } else { + pkgName = sysPkg.getManifestPackageName(); + } + return (implicitlyWhitelist && !userTypeWhitelist.containsKey(pkgName)) || userWhitelist.contains(pkgName); } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 34998a025663..b9240c78d711 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -898,11 +898,16 @@ final class AccessibilityController { /* ignore */ } mSurfaceControl = surfaceControl; - mService.mTransactionFactory.get().setLayer(mSurfaceControl, - mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY) - * WindowManagerService.TYPE_LAYER_MULTIPLIER) - .setPosition(mSurfaceControl, 0, 0) - .apply(); + + final SurfaceControl.Transaction t = mService.mTransactionFactory.get(); + final int layer = + mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY) * + WindowManagerService.TYPE_LAYER_MULTIPLIER; + t.setLayer(mSurfaceControl, layer).setPosition(mSurfaceControl, 0, 0); + InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t, + mDisplayContent.getDisplayId(), "Magnification Overlay"); + t.apply(); + mSurface.copyFrom(mSurfaceControl); mAnimationController = new AnimationController(context, diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index f38a506dd460..be2f9d410475 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7540,7 +7540,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A public String toString() { if (stringName != null) { return stringName + " t" + (task == null ? INVALID_TASK_ID : task.mTaskId) + - (finishing ? " f}" : "") + (mIsExiting ? " mIsExiting=" : "") + "}"; + (finishing ? " f}" : "") + (mIsExiting ? " isExiting" : "") + "}"; } StringBuilder sb = new StringBuilder(128); sb.append("ActivityRecord{"); diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index c99980911cef..4bede4c3623f 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -3232,22 +3232,22 @@ class ActivityStack extends Task { @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { - pw.println(prefix + "mStackId=" + getRootTaskId()); - pw.println(prefix + "mDeferRemoval=" + mDeferRemoval); - pw.println(prefix + "mBounds=" + getRawBounds().toShortString()); - for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) { - mChildren.get(taskNdx).dump(pw, prefix + " ", dumpAll); + if (mDeferRemoval) { + pw.println(prefix + "mDeferRemoval=true"); } + super.dump(pw, prefix, dumpAll); if (!mExitingActivities.isEmpty()) { pw.println(); - pw.println(" Exiting application tokens:"); + pw.println(prefix + "Exiting application tokens:"); + final String doublePrefix = prefix + " "; for (int i = mExitingActivities.size() - 1; i >= 0; i--) { WindowToken token = mExitingActivities.get(i); - pw.print(" Exiting App #"); pw.print(i); + pw.print(doublePrefix + "Exiting App #" + i); pw.print(' '); pw.print(token); pw.println(':'); - token.dump(pw, " ", dumpAll); + token.dump(pw, doublePrefix, dumpAll); } + pw.println(); } mAnimatingActivityRegistry.dump(pw, "AnimatingApps:", prefix); } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index b7ca1a9aeab8..1f9e8609c2ad 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -1886,7 +1886,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { public void dump(PrintWriter pw, String prefix) { pw.println(); pw.println("ActivityStackSupervisor state:"); - mRootWindowContainer.dump(pw, prefix); + mRootWindowContainer.dump(pw, prefix, true /* dumpAll */); getKeyguardController().dump(pw, prefix); mService.getLockTaskController().dump(pw, prefix); pw.print(prefix); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 5f591b54b067..6dd1ea934497 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1685,6 +1685,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final NeededUriGrants resultGrants = collectGrants(resultData, r.resultTo); synchronized (mGlobalLock) { + // Sanity check in case activity was removed before entering global lock. + if (!r.isInHistory()) { + return true; + } + // Keep track of the root activity of the task before we finish it final Task tr = r.getTask(); final ActivityRecord rootR = tr.getRootActivity(); @@ -4873,6 +4878,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return; } + if (isInPictureInPictureMode(activity)) { + throw new IllegalStateException("Activity is already in PIP mode"); + } + final boolean canEnterPictureInPicture = activity.checkEnterPictureInPictureState( "requestPictureInPictureMode", /* beforeStopping */ false); if (!canEnterPictureInPicture) { diff --git a/services/core/java/com/android/server/wm/AlertWindowNotification.java b/services/core/java/com/android/server/wm/AlertWindowNotification.java index 7b511ec20541..fde036950245 100644 --- a/services/core/java/com/android/server/wm/AlertWindowNotification.java +++ b/services/core/java/com/android/server/wm/AlertWindowNotification.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; +import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.content.Context.NOTIFICATION_SERVICE; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -139,7 +140,8 @@ class AlertWindowNotification { Uri.fromParts("package", packageName, null)); intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); // Calls into activity manager... - return PendingIntent.getActivity(context, mRequestCode, intent, FLAG_CANCEL_CURRENT); + return PendingIntent.getActivity(context, mRequestCode, intent, + FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE); } private void createNotificationChannel(Context context, String appName) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a5b94b327699..4e19a5224bb4 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2977,12 +2977,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo pw.println(); mWallpaperController.dump(pw, " "); - pw.println(); - pw.print("mSystemGestureExclusion="); if (mSystemGestureExclusionListeners.getRegisteredCallbackCount() > 0) { + pw.println(); + pw.print(" mSystemGestureExclusion="); pw.println(mSystemGestureExclusion); - } else { - pw.println("<no lstnrs>"); } pw.println(); @@ -3506,22 +3504,21 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * doesn't support IME/system decorations. * * @param target current IME target. - * @return {@link WindowState} that can host IME. + * @return {@link InsetsControlTarget} that can host IME. */ - WindowState getImeHostOrFallback(WindowState target) { + InsetsControlTarget getImeHostOrFallback(WindowState target) { if (target != null && target.getDisplayContent().canShowIme()) { return target; } return getImeFallback(); } - WindowState getImeFallback() { - + InsetsControlTarget getImeFallback() { // host is in non-default display that doesn't support system decor, default to - // default display's StatusBar to control IME. - // TODO: (b/148234093)find a better host OR control IME animation/visibility directly - // because it won't work when statusbar isn't available. - return mWmService.getDefaultDisplayContentLocked().getDisplayPolicy().getStatusBar(); + // default display's StatusBar to control IME (when available), else let system control it. + WindowState statusBar = + mWmService.getDefaultDisplayContentLocked().getDisplayPolicy().getStatusBar(); + return statusBar != null ? statusBar : mRemoteInsetsControlTarget; } boolean canShowIme() { diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java index 2165b0e8b593..c9cc94423fe2 100644 --- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java +++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java @@ -66,6 +66,9 @@ class EmulatorDisplayOverlay { t.setLayer(ctrl, zOrder); t.setPosition(ctrl, 0, 0); t.show(ctrl); + // Ensure we aren't considered as obscuring for Input purposes. + InputMonitor.setTrustedOverlayInputInfo(ctrl, t, + dc.getDisplayId(), "EmulatorDisplayOverlay"); mSurface.copyFrom(ctrl); } catch (OutOfResourcesException e) { } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index efcd61df5c75..8734b5efa45d 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -16,13 +16,17 @@ package com.android.server.wm; +import static android.os.Process.myPid; +import static android.os.Process.myUid; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; import static android.view.WindowManager.INPUT_CONSUMER_PIP; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS; +import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; @@ -550,4 +554,26 @@ final class InputMonitor { } } } + + /** + * Helper function to generate an InputInfo with type SECURE_SYSTEM_OVERLAY. This input + * info will not have an input channel or be touchable, but is used to omit Surfaces + * from occlusion detection, so that System global overlays like the Watermark aren't + * counted by the InputDispatcher as occluding applications below. + */ + static void setTrustedOverlayInputInfo(SurfaceControl sc, SurfaceControl.Transaction t, + int displayId, String name) { + InputWindowHandle inputWindowHandle = new InputWindowHandle(null, displayId); + inputWindowHandle.name = name; + inputWindowHandle.layoutParamsType = TYPE_SECURE_SYSTEM_OVERLAY; + inputWindowHandle.dispatchingTimeoutNanos = -1; + inputWindowHandle.visible = true; + inputWindowHandle.canReceiveKeys = false; + inputWindowHandle.hasFocus = false; + inputWindowHandle.ownerPid = myPid(); + inputWindowHandle.ownerUid = myUid(); + inputWindowHandle.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL; + inputWindowHandle.scaleFactor = 1; + t.setInputWindowInfo(sc, inputWindowHandle); + } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ae5adcae5b9b..583663c5455f 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -3570,12 +3570,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } - public void dump(PrintWriter pw, String prefix) { + @Override + public void dump(PrintWriter pw, String prefix, boolean dumpAll) { + super.dump(pw, prefix, dumpAll); pw.print(prefix); pw.println("topDisplayFocusedStack=" + getTopDisplayFocusedStack()); for (int i = getChildCount() - 1; i >= 0; --i) { final DisplayContent display = getChildAt(i); - display.dump(pw, prefix, true /* dumpAll */); + display.dump(pw, prefix, dumpAll); } pw.println(); } diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java index f537005c955c..fa62daaff3fc 100644 --- a/services/core/java/com/android/server/wm/StrictModeFlash.java +++ b/services/core/java/com/android/server/wm/StrictModeFlash.java @@ -54,6 +54,10 @@ class StrictModeFlash { t.setLayer(ctrl, WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); t.setPosition(ctrl, 0, 0); t.show(ctrl); + // Ensure we aren't considered as obscuring for Input purposes. + InputMonitor.setTrustedOverlayInputInfo(ctrl, t, dc.getDisplayId(), + "StrictModeFlash"); + mSurface.copyFrom(ctrl); } catch (OutOfResourcesException e) { } diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 1b77fd2e8782..0e5d7d910084 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -428,16 +428,11 @@ class SurfaceAnimator { void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mLeash="); pw.print(mLeash); - if (mAnimationStartDelayed) { - pw.print(" mAnimationStartDelayed="); pw.println(mAnimationStartDelayed); - } else { - pw.println(); - } - pw.print(prefix); pw.println("Animation:"); + pw.print(" mAnimationType=" + mAnimationType); + pw.println(mAnimationStartDelayed ? " mAnimationStartDelayed=true" : ""); + pw.print(prefix); pw.print("Animation: "); pw.println(mAnimation); if (mAnimation != null) { mAnimation.dump(pw, prefix + " "); - } else { - pw.print(prefix); pw.println("null"); } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 48609e17ba40..3ee7ee7a4276 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3519,20 +3519,17 @@ class Task extends WindowContainer<WindowContainer> { @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { super.dump(pw, prefix, dumpAll); + pw.println(prefix + "bounds=" + getBounds().toShortString()); final String doublePrefix = prefix + " "; - - pw.println(prefix + "taskId=" + mTaskId); - pw.println(doublePrefix + "mBounds=" + getBounds().toShortString()); - pw.println(doublePrefix + "appTokens=" + mChildren); - - final String triplePrefix = doublePrefix + " "; - final String quadruplePrefix = triplePrefix + " "; - - int[] index = { 0 }; - forAllActivities((r) -> { - pw.println(triplePrefix + "Activity #" + index[0]++ + " " + r); - r.dump(pw, quadruplePrefix, dumpAll); - }); + for (int i = mChildren.size() - 1; i >= 0; i--) { + final WindowContainer<?> child = mChildren.get(i); + pw.println(prefix + "* " + child); + // Only dump non-activity because full activity info is already printed by + // RootWindowContainer#dumpActivities. + if (child.asActivityRecord() == null) { + child.dump(pw, doublePrefix, dumpAll); + } + } } /** diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 22054db20bbd..102c2a6364f4 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1772,16 +1772,20 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { pw.println(prefix + "TaskDisplayArea " + getName()); + super.dump(pw, prefix, dumpAll); if (mPreferredTopFocusableStack != null) { pw.println(prefix + " mPreferredTopFocusableStack=" + mPreferredTopFocusableStack); } if (mLastFocusedStack != null) { pw.println(prefix + " mLastFocusedStack=" + mLastFocusedStack); } - pw.println(prefix + " Application tokens in top down Z order:"); + final String doublePrefix = prefix + " "; + final String triplePrefix = doublePrefix + " "; + pw.println(doublePrefix + "Application tokens in top down Z order:"); for (int stackNdx = getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = getChildAt(stackNdx); - stack.dump(pw, prefix + " ", dumpAll); + pw.println(doublePrefix + "* " + stack); + stack.dump(pw, triplePrefix, dumpAll); } } } diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java index 4e1b2177c87c..3d49ebe306e6 100644 --- a/services/core/java/com/android/server/wm/Watermark.java +++ b/services/core/java/com/android/server/wm/Watermark.java @@ -29,6 +29,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.Display; +import android.view.InputWindowHandle; import android.view.Surface; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; @@ -124,6 +125,8 @@ class Watermark { t.setLayer(ctrl, WindowManagerService.TYPE_LAYER_MULTIPLIER * 100) .setPosition(ctrl, 0, 0) .show(ctrl); + // Ensure we aren't considered as obscuring for Input purposes. + InputMonitor.setTrustedOverlayInputInfo(ctrl, t, dc.getDisplayId(), "Watermark"); mSurface.copyFrom(ctrl); } catch (OutOfResourcesException e) { } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a8f3ae5f24a3..0590288a7f8b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7623,8 +7623,12 @@ public class WindowManagerService extends IWindowManager.Stub if (imeTarget == null) { return; } - imeTarget = imeTarget.getImeControlTarget(); - imeTarget.getDisplayContent().getInsetsStateController().getImeSourceProvider() + imeTarget = imeTarget.getImeControlTarget().getWindow(); + // If InsetsControlTarget doesn't have a window, its using remoteControlTarget which + // is controlled by default display + final DisplayContent dc = imeTarget != null + ? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked(); + dc.getInsetsStateController().getImeSourceProvider() .scheduleShowImePostLayout(imeTarget); } } @@ -7637,7 +7641,9 @@ public class WindowManagerService extends IWindowManager.Stub // The target window no longer exists. return; } - final DisplayContent dc = imeTarget.getImeControlTarget().getDisplayContent(); + imeTarget = imeTarget.getImeControlTarget().getWindow(); + final DisplayContent dc = imeTarget != null + ? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked(); // If there was a pending IME show(), reset it as IME has been // requested to be hidden. dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 36232e13fcf1..fe3ee50c34c5 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5404,10 +5404,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} is unknown, * use {@link DisplayContent#getImeControlTarget()} instead. * - * @return {@link WindowState} of host that controls the IME. + * @return {@link InsetsControlTarget} of host that controls the IME. * When window is doesn't have a parent, it is returned as-is. */ - WindowState getImeControlTarget() { + InsetsControlTarget getImeControlTarget() { final DisplayContent dc = getDisplayContent(); final WindowState parentWindow = dc.getParentWindow(); diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index ff34ebd8aa9d..b4e0f10f14b0 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -21,7 +21,7 @@ android_test { "services.core", "services.net", "service-jobscheduler", - "service-permission", + "service-permission.impl", "service-blobstore", "androidx.test.runner", "androidx.test.ext.truth", diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 40a17061c7f1..979f4e179e95 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -44,7 +44,7 @@ android_test { "hamcrest-library", "servicestests-utils", "service-jobscheduler", - "service-permission", + "service-permission.impl", // TODO: remove once Android migrates to JUnit 4.12, // which provides assertThrows "testng", diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk Binary files differindex 8056e0bf6e50..211e064399a8 100644 --- a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk +++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk 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 53c9bb22e752..9ca84d33998c 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -121,7 +121,7 @@ public class AppIntegrityManagerServiceImplTest { private static final String INSTALLER_SHA256 = "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227"; private static final String SOURCE_STAMP_CERTIFICATE_HASH = - "681B0E56A796350C08647352A4DB800CC44B2ADC8F4C72FA350BD05D4D50264D"; + "C6E737809CEF2B08CC6694892215F82A5E8FBC3C2A0F6212770310B90622D2D9"; private static final String DUMMY_APP_TWO_CERTS_CERT_1 = "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94"; diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java index c55df512bd1e..87979fb00021 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java @@ -366,9 +366,75 @@ public class UserSystemPackageInstallerTest { for (PackageInfo p : packageInfos) { actualPackages.add(p.packageName); } + + // Add auto-generated RRO package to expectedPackages since they are not (supposed to be) + // in the whitelist but they should be installed. + for (PackageInfo p : packageInfos) { + if (p.isOverlayPackage() + && UserSystemPackageInstaller.hasAutoGeneratedRROSuffix(p.packageName) + && expectedPackages.contains(p.overlayTarget)) { + expectedPackages.add(p.packageName); + } + } checkPackageDifferences(expectedPackages, actualPackages); } + @Test + public void testAutoGeneratedRROMatchesSuffix() { + final List<PackageInfo> packageInfos = mContext.getPackageManager() + .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES); + + Log.v(TAG, "Found total packages: " + packageInfos.size()); + + for (PackageInfo p : packageInfos) { + if (p.packageName.contains(".auto_generated_rro_")) { + assertTrue("Auto-generated RRO package name does not match the suffix: " + + p.packageName, + UserSystemPackageInstaller.hasAutoGeneratedRROSuffix(p.packageName)); + } + } + } + + /** + * Test that overlay package not in whitelist should be installed for all user at Explicit mode. + */ + @Test + public void testInstallOverlayPackagesExplicitMode() { + setUserTypePackageWhitelistMode(USER_TYPE_PACKAGE_WHITELIST_MODE_ENFORCE); + + final String[] userTypes = new String[]{"type"}; + final long maskOfType = 0b0001L; + + final String packageName1 = "whitelistedPkg"; + final String packageName2 = "nonWhitelistedPkg"; + final String overlayName1 = String.format("%s.auto_generated_rro_product__", packageName1); + final String overlayName2 = String.format("%s.auto_generated_rro_product__", packageName2); + + final AndroidPackage overlayPackage1 = ((ParsedPackage) PackageImpl.forTesting(overlayName1) + .setOverlay(true) + .setOverlayTarget(packageName1) + .hideAsParsed()) + .hideAsFinal(); + + final AndroidPackage overlayPackage2 = ((ParsedPackage) PackageImpl.forTesting(overlayName2) + .setOverlay(true) + .setOverlayTarget(packageName2) + .hideAsParsed()) + .hideAsFinal(); + + final ArrayMap<String, Long> userTypeWhitelist = new ArrayMap<>(); + userTypeWhitelist.put(packageName1, maskOfType); + + final Set<String> userWhitelist = new ArraySet<>(); + userWhitelist.add(packageName1); + + boolean implicit = false; + assertTrue("Overlay for package1 should be installed", UserSystemPackageInstaller + .shouldInstallPackage(overlayPackage1, userTypeWhitelist, userWhitelist, implicit)); + assertFalse("Overlay for package2 should not be installed", UserSystemPackageInstaller + .shouldInstallPackage(overlayPackage2, userTypeWhitelist, userWhitelist, implicit)); + } + /** Asserts that actual is a subset of expected. */ private void checkPackageDifferences(Set<String> expected, Set<String> actual) { final Set<String> uniqueToExpected = new ArraySet<>(expected); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index de9b77c68336..9b8bb3592fc9 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -178,6 +178,7 @@ import com.android.server.wm.WindowManagerInternal; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -1328,6 +1329,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @Ignore public void testPostCancelPostNotifiesListeners() throws Exception { // WHEN a notification is posted final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); @@ -5455,25 +5457,49 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testCancelNotificationsFromListener_ignoresBubbles() throws Exception { - final NotificationRecord nrNormal = generateNotificationRecord(mTestNotificationChannel); - final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel); - nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE; + public void testCancelNotificationsFromListener_cancelsNonBubble() throws Exception { + // Add non-bubble notif + final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(nr); - mService.addNotification(nrNormal); - mService.addNotification(nrBubble); + // Cancel via listener + String[] keys = {nr.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + + // Notif not active anymore + StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); + assertEquals(0, notifs.length); + assertEquals(0, mService.getNotificationRecordCount()); + // Cancel event is logged + assertEquals(1, mNotificationRecordLogger.numCalls()); + assertEquals(NotificationRecordLogger.NotificationCancelledEvent + .NOTIFICATION_CANCEL_LISTENER_CANCEL, mNotificationRecordLogger.event(0)); + } + + @Test + public void testCancelNotificationsFromListener_suppressesBubble() throws Exception { + // Add bubble notif + setUpPrefsForBubbles(PKG, mUid, + true /* global */, + BUBBLE_PREFERENCE_ALL /* app */, + true /* channel */); + NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag"); - String[] keys = {nrNormal.getSbn().getKey(), nrBubble.getSbn().getKey()}; + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + // Cancel via listener + String[] keys = {nr.getSbn().getKey()}; mService.getBinderService().cancelNotificationsFromListener(null, keys); waitForIdle(); + // Bubble notif active and suppressed StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG); assertEquals(1, notifs.length); assertEquals(1, mService.getNotificationRecordCount()); - - assertEquals(1, mNotificationRecordLogger.numCalls()); - assertEquals(NotificationRecordLogger.NotificationCancelledEvent - .NOTIFICATION_CANCEL_LISTENER_CANCEL, mNotificationRecordLogger.event(0)); + assertTrue(notifs[0].getNotification().getBubbleMetadata().isNotificationSuppressed()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java b/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java index f4f64d779d30..89adc724f600 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/WrappedSysUiStatsEvent.java @@ -88,11 +88,21 @@ public class WrappedSysUiStatsEvent { return index < mValues.size() ? mValues.get(index) : null; } - /** useful to make assertTrue() statemetns more readable. */ + /** useful to make assertTrue() statements more readable. */ public boolean getBoolean(int index) { return (Boolean) mValues.get(index); } + /** useful to make assertTrue() statements more readable. */ + public int getInt(int index) { + return (Integer) mValues.get(index); + } + + /** useful to make assertTrue() statements more readable. */ + public String getString(int index) { + return (String) mValues.get(index); + } + private void addValue(Object value) { mLastIndex = mValues.size(); mValues.add(value); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index f7b435ed937b..013a99433041 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -101,11 +101,39 @@ public class ZenModeConfigTest extends UiServiceTestCase { Policy expectedPolicy = new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders, suppressedVisualEffects, 0, priorityConversationsSenders); - assertEquals(expectedPolicy, config.toNotificationPolicy(zenPolicy)); } @Test + public void testZenConfigToZenPolicy() { + ZenPolicy expected = new ZenPolicy.Builder() + .allowAlarms(true) + .allowReminders(true) + .allowEvents(true) + .showLights(false) + .showBadges(false) + .showInAmbientDisplay(false) + .build(); + + ZenModeConfig config = getMutedAllConfig(); + config.allowAlarms = true; + config.allowReminders = true; + config.allowEvents = true; + config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE; + config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS; + config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT; + ZenPolicy actual = config.toZenPolicy(); + + assertEquals(expected.getVisualEffectBadge(), actual.getVisualEffectBadge()); + assertEquals(expected.getPriorityCategoryAlarms(), actual.getPriorityCategoryAlarms()); + assertEquals(expected.getPriorityCategoryReminders(), + actual.getPriorityCategoryReminders()); + assertEquals(expected.getPriorityCategoryEvents(), actual.getPriorityCategoryEvents()); + assertEquals(expected.getVisualEffectLights(), actual.getVisualEffectLights()); + assertEquals(expected.getVisualEffectAmbient(), actual.getVisualEffectAmbient()); + } + + @Test public void testPriorityOnlyMutingAll() { ZenModeConfig config = getMutedAllConfig(); assertTrue(ZenModeConfig.areAllPriorityOnlyRingerSoundsMuted(config)); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 108af892c7dd..3c7206fee9d1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -27,16 +27,25 @@ import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; -import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + +import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; +import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; +import static com.android.os.AtomsProto.DNDModeProto.CHANNELS_BYPASSING_FIELD_NUMBER; +import static com.android.os.AtomsProto.DNDModeProto.ENABLED_FIELD_NUMBER; +import static com.android.os.AtomsProto.DNDModeProto.ID_FIELD_NUMBER; +import static com.android.os.AtomsProto.DNDModeProto.UID_FIELD_NUMBER; +import static com.android.os.AtomsProto.DNDModeProto.ZEN_MODE_FIELD_NUMBER; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.fail; import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.any; @@ -70,10 +79,12 @@ import android.media.AudioManagerInternal; import android.media.AudioSystem; import android.media.VolumePolicy; import android.net.Uri; +import android.os.Process; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Condition; +import android.service.notification.DNDModeProto; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ScheduleInfo; import android.service.notification.ZenPolicy; @@ -82,6 +93,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; import android.util.Log; +import android.util.StatsEvent; import android.util.Xml; import com.android.internal.R; @@ -106,6 +118,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; import java.util.Objects; @SmallTest @@ -115,6 +130,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE"; private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE"; + private static final int ZEN_MODE_FOR_TESTING = 99; ConditionProviders mConditionProviders; @Mock NotificationManager mNotificationManager; @@ -124,6 +140,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { private Context mContext; private ContentResolver mContentResolver; @Mock AppOpsManager mAppOps; + private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory; @Before public void setUp() { @@ -140,6 +157,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { Log.d("ZenModeHelperTest", "Couldn't mock default zen mode config xml file err=" + e.toString()); } + mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory(); when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOps); when(mContext.getSystemService(NotificationManager.class)).thenReturn(mNotificationManager); @@ -147,7 +165,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { AppGlobals.getPackageManager()); mConditionProviders.addSystemProvider(new CountdownConditionProvider()); mZenModeHelperSpy = spy(new ZenModeHelper(mContext, mTestableLooper.getLooper(), - mConditionProviders)); + mConditionProviders, mStatsEventBuilderFactory)); } private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException { @@ -212,6 +230,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { } private ArrayMap<String, ZenModeConfig.ZenRule> getCustomAutomaticRules() { + return getCustomAutomaticRules(ZEN_MODE_IMPORTANT_INTERRUPTIONS); + } + + private ArrayMap<String, ZenModeConfig.ZenRule> getCustomAutomaticRules(int zenMode) { ArrayMap<String, ZenModeConfig.ZenRule> automaticRules = new ArrayMap<>(); ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule(); final ScheduleInfo customRuleInfo = new ScheduleInfo(); @@ -219,10 +241,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { customRule.creationTime = 0; customRule.id = "customRule"; customRule.name = "Custom Rule"; - customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customRule.zenMode = zenMode; customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo); customRule.configurationActivity = - new ComponentName("android", "ScheduleConditionProvider"); + new ComponentName("not.android", "ScheduleConditionProvider"); customRule.pkg = customRule.configurationActivity.getPackageName(); automaticRules.put("customRule", customRule); return automaticRules; @@ -244,7 +266,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_NotificationApplied() { - mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; // The most permissive policy mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES @@ -267,7 +289,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_StarredCallers_CallTypesBlocked() { - mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; // The most permissive policy mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES @@ -287,7 +309,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_AllCallers_CallTypesAllowed() { - mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; // The most permissive policy mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES @@ -307,7 +329,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_AllowAlarmsMedia_NoAlarmMediaMuteApplied() { - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0); @@ -320,7 +342,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_DisallowAlarmsMedia_AlarmMediaMuteApplied() { - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true, true, @@ -406,7 +428,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testZenAllCannotBypass() { // Only audio attributes with SUPPRESIBLE_NEVER can bypass // with special case USAGE_ASSISTANCE_SONIFICATION - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); @@ -428,7 +450,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testApplyRestrictions_whitelist_priorityOnlyMode() { mZenModeHelperSpy.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); - mZenModeHelperSpy.mZenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelperSpy.applyRestrictions(); @@ -479,7 +501,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0); mZenModeHelperSpy.mIsBootComplete = true; mZenModeHelperSpy.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); - mZenModeHelperSpy.setZenModeSetting(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); + mZenModeHelperSpy.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS); verify(mZenModeHelperSpy, times(1)).createZenUpgradeNotification(); verify(mNotificationManager, times(1)).notify(eq(ZenModeHelper.TAG), @@ -494,7 +516,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0); Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0); mZenModeHelperSpy.mIsBootComplete = true; - mZenModeHelperSpy.setZenModeSetting(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); + mZenModeHelperSpy.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS); verify(mZenModeHelperSpy, never()).createZenUpgradeNotification(); verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG), @@ -507,7 +529,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0); Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 1); mZenModeHelperSpy.mIsBootComplete = true; - mZenModeHelperSpy.setZenModeSetting(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS); + mZenModeHelperSpy.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS); verify(mZenModeHelperSpy, never()).createZenUpgradeNotification(); verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG), @@ -524,7 +546,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // 1. Current ringer is normal when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); // Set zen to priority-only with all notification sounds muted (so ringer will be muted) - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.allowReminders = false; mZenModeHelperSpy.mConfig.allowCalls = false; mZenModeHelperSpy.mConfig.allowMessages = false; @@ -568,7 +590,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // ringtone, notification and system streams are affected by ringer mode mZenModeHelperSpy.mConfig.allowAlarms = true; mZenModeHelperSpy.mConfig.allowReminders = true; - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; ZenModeHelper.RingerModeDelegate ringerModeDelegateRingerMuted = mZenModeHelperSpy.new RingerModeDelegate(); @@ -615,7 +637,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // 1. Current ringer is normal when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.allowReminders = true; // 2. apply priority only zen - verify ringer is normal @@ -640,7 +662,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // 1. Current ringer is silent when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.allowReminders = true; // 2. apply priority only zen - verify ringer is silent @@ -666,7 +688,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // 1. Current ringer is normal when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); // Set zen to priority-only with all notification sounds muted (so ringer will be muted) - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.allowReminders = true; // 2. apply priority only zen - verify zen will still be normal @@ -728,7 +750,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // apply zen off multiple times - verify ringer is not set to normal when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer mZenModeHelperSpy.evaluateZenMode("test", true); @@ -756,7 +778,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // apply zen off multiple times - verify ringer is not set to normal when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE); - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer mZenModeHelperSpy.evaluateZenMode("test", true); @@ -768,7 +790,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testParcelConfig() { - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.allowAlarms = false; mZenModeHelperSpy.mConfig.allowMedia = false; mZenModeHelperSpy.mConfig.allowSystem = false; @@ -792,7 +814,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testWriteXml() throws Exception { - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.allowAlarms = false; mZenModeHelperSpy.mConfig.allowMedia = false; mZenModeHelperSpy.mConfig.allowSystem = false; @@ -806,7 +828,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelperSpy.mConfig.suppressedVisualEffects = SUPPRESSED_EFFECT_BADGE; mZenModeHelperSpy.mConfig.manualRule = new ZenModeConfig.ZenRule(); mZenModeHelperSpy.mConfig.manualRule.zenMode = - Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.manualRule.component = new ComponentName("a", "a"); mZenModeHelperSpy.mConfig.manualRule.pkg = "a"; mZenModeHelperSpy.mConfig.manualRule.enabled = true; @@ -822,6 +844,102 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + public void testProto() { + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mConfig.manualRule = new ZenModeConfig.ZenRule(); + + int n = mZenModeHelperSpy.mConfig.automaticRules.size(); + List<String> ids = new ArrayList<>(n); + for (ZenModeConfig.ZenRule rule : mZenModeHelperSpy.mConfig.automaticRules.values()) { + ids.add(rule.id); + } + ids.add(""); + + List<StatsEvent> events = new LinkedList<>(); + mZenModeHelperSpy.pullRules(events); + assertEquals(n + 1, events.size()); + for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { + if (builder.getAtomId() == DND_MODE_RULE) { + if (builder.getInt(ZEN_MODE_FIELD_NUMBER) == DNDModeProto.ROOT_CONFIG) { + assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER)); + assertFalse(builder.getBoolean(CHANNELS_BYPASSING_FIELD_NUMBER)); + } + assertEquals(Process.SYSTEM_UID, builder.getInt(UID_FIELD_NUMBER)); + assertTrue(builder.getBooleanAnnotation(UID_FIELD_NUMBER, ANNOTATION_ID_IS_UID)); + String name = (String) builder.getValue(ID_FIELD_NUMBER); + assertTrue("unexpected rule id", ids.contains(name)); + ids.remove(name); + } else { + fail("unexpected atom id: " + builder.getAtomId()); + } + } + assertEquals("extra rule in output", 0, ids.size()); + } + + @Test + public void testProtoWithAutoRule() throws Exception { + setupZenConfig(); + // one enabled automatic rule + mZenModeHelperSpy.mConfig.automaticRules = getCustomAutomaticRules(ZEN_MODE_FOR_TESTING); + + List<StatsEvent> events = new LinkedList<>(); + mZenModeHelperSpy.pullRules(events); + + boolean foundCustomEvent = false; + for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { + if (builder.getAtomId() == DND_MODE_RULE) { + if (ZEN_MODE_FOR_TESTING == builder.getInt(ZEN_MODE_FIELD_NUMBER)) { + foundCustomEvent = true; + assertEquals(0, builder.getInt(UID_FIELD_NUMBER)); + assertTrue(builder.getBoolean(ENABLED_FIELD_NUMBER)); + } + } else { + fail("unexpected atom id: " + builder.getAtomId()); + } + } + assertTrue("couldn't find custom rule", foundCustomEvent); + } + + @Test + public void testProtoRedactsIds() throws Exception { + setupZenConfig(); + // one enabled automatic rule + mZenModeHelperSpy.mConfig.automaticRules = getCustomAutomaticRules(); + + List<StatsEvent> events = new LinkedList<>(); + mZenModeHelperSpy.pullRules(events); + + boolean foundCustomEvent = false; + for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { + if (builder.getAtomId() == DND_MODE_RULE + && "customRule".equals(builder.getString(ID_FIELD_NUMBER))) { + fail("non-default IDs should be redacted"); + } + } + } + + @Test + public void testProtoWithManualRule() throws Exception { + setupZenConfig(); + mZenModeHelperSpy.mConfig.automaticRules = getCustomAutomaticRules(); + mZenModeHelperSpy.mConfig.manualRule = new ZenModeConfig.ZenRule(); + mZenModeHelperSpy.mConfig.manualRule.enabled = true; + mZenModeHelperSpy.mConfig.manualRule.enabler = "com.enabler"; + + List<StatsEvent> events = new LinkedList<>(); + mZenModeHelperSpy.pullRules(events); + + boolean foundManualRule = false; + for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) { + if (builder.getAtomId() == DND_MODE_RULE + && ZenModeConfig.MANUAL_RULE_ID.equals(builder.getString(ID_FIELD_NUMBER))) { + assertEquals(0, builder.getInt(UID_FIELD_NUMBER)); + foundManualRule = true; + } + } + assertTrue("couldn't find manual rule", foundManualRule); } + + @Test public void testWriteXml_onlyBackupsTargetUser() throws Exception { // Setup configs for user 10 and 11. setupZenConfig(); @@ -906,7 +1024,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { customRule.creationTime = 0; customRule.id = "customRule"; customRule.name = "Custom Rule"; - customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo); customRule.configurationActivity = new ComponentName("android", "ScheduleConditionProvider"); @@ -951,7 +1069,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { customRule.creationTime = 0; customRule.id = ruleId; customRule.name = "Custom Rule"; - customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo); customRule.configurationActivity = new ComponentName("android", "ScheduleConditionProvider"); @@ -986,7 +1104,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { final ScheduleInfo weeknights = new ScheduleInfo(); customRule.enabled = true; customRule.name = "Custom Rule"; - customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights); customRule.component = new ComponentName("android", "ScheduleConditionProvider"); enabledAutoRule.put("customRule", customRule); @@ -1151,7 +1269,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { final ScheduleInfo weeknights = new ScheduleInfo(); customRule.enabled = false; customRule.name = "Custom Rule"; - customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights); customRule.component = new ComponentName("android", "ScheduleConditionProvider"); disabledAutoRule.put("customRule", customRule); @@ -1187,7 +1305,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { final ScheduleInfo customRuleInfo = new ScheduleInfo(); customRule.enabled = false; customRule.name = "Custom Rule"; - customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo); customRule.component = new ComponentName("android", "ScheduleConditionProvider"); customRule.zenPolicy = new ZenPolicy.Builder() @@ -1200,7 +1318,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { final ScheduleInfo defaultScheduleRuleInfo = new ScheduleInfo(); defaultScheduleRule.enabled = false; defaultScheduleRule.name = "Default Schedule Rule"; - defaultScheduleRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + defaultScheduleRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; defaultScheduleRule.conditionId = ZenModeConfig.toScheduleConditionId( defaultScheduleRuleInfo); customRule.component = new ComponentName("android", "ScheduleConditionProvider"); @@ -1238,7 +1356,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { final ScheduleInfo customRuleInfo = new ScheduleInfo(); customRule.enabled = false; customRule.name = "Custom Rule"; - customRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; customRule.conditionId = ZenModeConfig.toScheduleConditionId(customRuleInfo); customRule.component = new ComponentName("android", "ScheduleConditionProvider"); customRule.zenPolicy = new ZenPolicy.Builder() @@ -1251,7 +1369,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { final ScheduleInfo defaultScheduleRuleInfo = new ScheduleInfo(); defaultScheduleRule.enabled = false; defaultScheduleRule.name = "Default Schedule Rule"; - defaultScheduleRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + defaultScheduleRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; defaultScheduleRule.conditionId = ZenModeConfig.toScheduleConditionId( defaultScheduleRuleInfo); defaultScheduleRule.id = ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID; @@ -1265,7 +1383,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { final ScheduleInfo defaultEventRuleInfo = new ScheduleInfo(); defaultEventRule.enabled = false; defaultEventRule.name = "Default Event Rule"; - defaultEventRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId( defaultEventRuleInfo); defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID; @@ -1295,6 +1413,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertTrue(rules.containsKey("customRule")); setupZenConfigMaintained(); + + List<StatsEvent> events = new LinkedList<>(); + mZenModeHelperSpy.pullRules(events); + assertEquals(4, events.size()); } @Test @@ -1323,10 +1445,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testEmptyDefaultRulesMap() { + List<StatsEvent> events = new LinkedList<>(); ZenModeConfig config = new ZenModeConfig(); config.automaticRules = new ArrayMap<>(); mZenModeHelperSpy.mConfig = config; mZenModeHelperSpy.updateDefaultZenRules(); // shouldn't throw null pointer + mZenModeHelperSpy.pullRules(events); // shouldn't throw null pointer } @Test @@ -1342,7 +1466,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { updatedDefaultRule.creationTime = 0; updatedDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID; updatedDefaultRule.name = "Schedule Default Rule"; - updatedDefaultRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + updatedDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; updatedDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(new ScheduleInfo()); updatedDefaultRule.component = new ComponentName("android", "ScheduleConditionProvider"); @@ -1368,7 +1492,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { updatedDefaultRule.creationTime = 0; updatedDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID; updatedDefaultRule.name = "Schedule Default Rule"; - updatedDefaultRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + updatedDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; updatedDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(new ScheduleInfo()); updatedDefaultRule.component = new ComponentName("android", "ScheduleConditionProvider"); @@ -1395,7 +1519,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { customDefaultRule.creationTime = 0; customDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID; customDefaultRule.name = "Schedule Default Rule"; - customDefaultRule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; customDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(new ScheduleInfo()); customDefaultRule.component = new ComponentName("android", "ScheduleConditionProvider"); @@ -1433,7 +1557,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } private void setupZenConfig() { - mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + mZenModeHelperSpy.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelperSpy.mConfig.allowAlarms = false; mZenModeHelperSpy.mConfig.allowMedia = false; mZenModeHelperSpy.mConfig.allowSystem = false; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java index a0ee417c8b47..b16ca8b92848 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java @@ -17,14 +17,18 @@ package com.android.server.notification; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.fail; import android.service.notification.ZenPolicy; +import android.service.notification.nano.DNDPolicyProto; import android.test.suitebuilder.annotation.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.UiServiceTestCase; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; + import org.junit.Test; import org.junit.runner.RunWith; @@ -190,6 +194,7 @@ public class ZenPolicyTest extends UiServiceTestCase { ZenPolicy policy = builder.build(); assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryMessages()); assertEquals(ZenPolicy.PEOPLE_TYPE_UNSET, policy.getPriorityMessageSenders()); + assertProtoMatches(policy, policy.toProto()); } @Test @@ -201,6 +206,7 @@ public class ZenPolicyTest extends UiServiceTestCase { ZenPolicy policy = builder.build(); assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls()); assertEquals(ZenPolicy.PEOPLE_TYPE_ANYONE, policy.getPriorityCallSenders()); + assertProtoMatches(policy, policy.toProto()); } @Test @@ -210,6 +216,7 @@ public class ZenPolicyTest extends UiServiceTestCase { ZenPolicy policy = builder.build(); assertAllPriorityCategoriesUnsetExcept(policy, -1); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); } @Test @@ -221,12 +228,14 @@ public class ZenPolicyTest extends UiServiceTestCase { assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_REMINDERS); assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryReminders()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowReminders(false); policy = builder.build(); assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_REMINDERS); assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryReminders()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); } @Test @@ -238,12 +247,14 @@ public class ZenPolicyTest extends UiServiceTestCase { assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_EVENTS); assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryEvents()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowEvents(false); policy = builder.build(); assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_EVENTS); assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryEvents()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); } @Test @@ -256,6 +267,7 @@ public class ZenPolicyTest extends UiServiceTestCase { assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMessages()); assertEquals(ZenPolicy.PEOPLE_TYPE_ANYONE, policy.getPriorityMessageSenders()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowMessages(ZenPolicy.PEOPLE_TYPE_CONTACTS); policy = builder.build(); @@ -263,6 +275,7 @@ public class ZenPolicyTest extends UiServiceTestCase { assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMessages()); assertEquals(ZenPolicy.PEOPLE_TYPE_CONTACTS, policy.getPriorityMessageSenders()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED); policy = builder.build(); @@ -270,6 +283,7 @@ public class ZenPolicyTest extends UiServiceTestCase { assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMessages()); assertEquals(ZenPolicy.PEOPLE_TYPE_STARRED, policy.getPriorityMessageSenders()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowMessages(ZenPolicy.PEOPLE_TYPE_NONE); policy = builder.build(); @@ -277,11 +291,13 @@ public class ZenPolicyTest extends UiServiceTestCase { assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryMessages()); assertEquals(ZenPolicy.PEOPLE_TYPE_NONE, policy.getPriorityMessageSenders()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowMessages(ZenPolicy.PEOPLE_TYPE_UNSET); policy = builder.build(); assertAllPriorityCategoriesUnsetExcept(policy, -1); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); } @Test @@ -294,6 +310,7 @@ public class ZenPolicyTest extends UiServiceTestCase { assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls()); assertEquals(ZenPolicy.PEOPLE_TYPE_ANYONE, policy.getPriorityCallSenders()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS); policy = builder.build(); @@ -301,6 +318,7 @@ public class ZenPolicyTest extends UiServiceTestCase { assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls()); assertEquals(ZenPolicy.PEOPLE_TYPE_CONTACTS, policy.getPriorityCallSenders()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowCalls(ZenPolicy.PEOPLE_TYPE_STARRED); policy = builder.build(); @@ -308,6 +326,7 @@ public class ZenPolicyTest extends UiServiceTestCase { assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryCalls()); assertEquals(ZenPolicy.PEOPLE_TYPE_STARRED, policy.getPriorityCallSenders()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowCalls(ZenPolicy.PEOPLE_TYPE_NONE); policy = builder.build(); @@ -315,11 +334,13 @@ public class ZenPolicyTest extends UiServiceTestCase { assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryCalls()); assertEquals(ZenPolicy.PEOPLE_TYPE_NONE, policy.getPriorityCallSenders()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowCalls(ZenPolicy.PEOPLE_TYPE_UNSET); policy = builder.build(); assertAllPriorityCategoriesUnsetExcept(policy, -1); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); } @Test @@ -331,12 +352,14 @@ public class ZenPolicyTest extends UiServiceTestCase { assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS); assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryRepeatCallers()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowRepeatCallers(false); policy = builder.build(); assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS); assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryRepeatCallers()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); } @Test @@ -348,12 +371,14 @@ public class ZenPolicyTest extends UiServiceTestCase { assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_ALARMS); assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryAlarms()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowAlarms(false); policy = builder.build(); assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_ALARMS); assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryAlarms()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); } @Test @@ -365,12 +390,14 @@ public class ZenPolicyTest extends UiServiceTestCase { assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_MEDIA); assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategoryMedia()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowMedia(false); policy = builder.build(); assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_MEDIA); assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategoryMedia()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); } @Test @@ -382,12 +409,119 @@ public class ZenPolicyTest extends UiServiceTestCase { assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_SYSTEM); assertEquals(ZenPolicy.STATE_ALLOW, policy.getPriorityCategorySystem()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); builder.allowSystem(false); policy = builder.build(); assertAllPriorityCategoriesUnsetExcept(policy, ZenPolicy.PRIORITY_CATEGORY_SYSTEM); assertEquals(ZenPolicy.STATE_DISALLOW, policy.getPriorityCategorySystem()); assertAllVisualEffectsUnsetExcept(policy, -1); + assertProtoMatches(policy, policy.toProto()); + } + + @Test + public void tesShowFullScreenIntent() { + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + + builder.showFullScreenIntent(true); + ZenPolicy policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT); + assertProtoMatches(policy, policy.toProto()); + + builder.showFullScreenIntent(false); + policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_FULL_SCREEN_INTENT); + assertProtoMatches(policy, policy.toProto()); + } + + @Test + public void tesShowLights() { + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + + builder.showLights(true); + ZenPolicy policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_LIGHTS); + assertProtoMatches(policy, policy.toProto()); + + builder.showLights(false); + policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_LIGHTS); + assertProtoMatches(policy, policy.toProto()); + } + + @Test + public void tesShowPeeking() { + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + + builder.showPeeking(true); + ZenPolicy policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_PEEK); + assertProtoMatches(policy, policy.toProto()); + + builder.showPeeking(false); + policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_PEEK); + assertProtoMatches(policy, policy.toProto()); + } + + @Test + public void tesShowStatusBarIcons() { + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + + builder.showStatusBarIcons(true); + ZenPolicy policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_STATUS_BAR); + assertProtoMatches(policy, policy.toProto()); + + builder.showStatusBarIcons(false); + policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_STATUS_BAR); + assertProtoMatches(policy, policy.toProto()); + } + + @Test + public void tesShowBadges() { + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + + builder.showBadges(true); + ZenPolicy policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_BADGE); + assertProtoMatches(policy, policy.toProto()); + + builder.showBadges(false); + policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_BADGE); + assertProtoMatches(policy, policy.toProto()); + } + + @Test + public void tesShowInAmbientDisplay() { + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + + builder.showInAmbientDisplay(true); + ZenPolicy policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_AMBIENT); + assertProtoMatches(policy, policy.toProto()); + + builder.showInAmbientDisplay(false); + policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_AMBIENT); + assertProtoMatches(policy, policy.toProto()); + } + + @Test + public void tesShowInNotificationList() { + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + + builder.showInNotificationList(true); + ZenPolicy policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST); + assertProtoMatches(policy, policy.toProto()); + + builder.showInNotificationList(false); + policy = builder.build(); + assertAllVisualEffectsUnsetExcept(policy, ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST); + assertProtoMatches(policy, policy.toProto()); } private void assertAllPriorityCategoriesUnsetExcept(ZenPolicy policy, int except) { @@ -453,4 +587,35 @@ public class ZenPolicyTest extends UiServiceTestCase { assertEquals(ZenPolicy.STATE_UNSET, policy.getVisualEffectNotificationList()); } } + + private void assertProtoMatches(ZenPolicy policy, byte[] bytes) { + try { + DNDPolicyProto proto = DNDPolicyProto.parseFrom(bytes); + + assertEquals(policy.getPriorityCategoryCalls(), proto.calls); + assertEquals(policy.getPriorityCategoryRepeatCallers(), proto.repeatCallers); + assertEquals(policy.getPriorityCategoryMessages(), proto.messages); + assertEquals(policy.getPriorityCategoryConversations(), proto.conversations); + assertEquals(policy.getPriorityCategoryReminders(), proto.reminders); + assertEquals(policy.getPriorityCategoryEvents(), proto.events); + assertEquals(policy.getPriorityCategoryAlarms(), proto.alarms); + assertEquals(policy.getPriorityCategoryMedia(), proto.media); + assertEquals(policy.getPriorityCategorySystem(), proto.system); + + assertEquals(policy.getVisualEffectFullScreenIntent(), proto.fullscreen); + assertEquals(policy.getVisualEffectLights(), proto.lights); + assertEquals(policy.getVisualEffectPeek(), proto.peek); + assertEquals(policy.getVisualEffectStatusBar(), proto.statusBar); + assertEquals(policy.getVisualEffectBadge(), proto.badge); + assertEquals(policy.getVisualEffectAmbient(), proto.ambient); + assertEquals(policy.getVisualEffectNotificationList(), proto.notificationList); + + assertEquals(policy.getPriorityCallSenders(), proto.allowCallsFrom); + assertEquals(policy.getPriorityMessageSenders(), proto.allowMessagesFrom); + assertEquals(policy.getPriorityConversationSenders(), proto.allowConversationsFrom); + } catch (InvalidProtocolBufferNanoException e) { + fail("could not parse proto bytes"); + } + + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index bd616a3a96f3..64b5eca1beb8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; @@ -111,7 +110,7 @@ public class ActivityTaskManagerServiceTests extends ActivityTestsBase { final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity(); ClientLifecycleManager lifecycleManager = mService.getLifecycleManager(); - doNothing().when(lifecycleManager).scheduleTransaction(any()); + doReturn(false).when(activity).inPinnedWindowingMode(); doReturn(false).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean()); mService.requestPictureInPictureMode(activity.token); @@ -120,6 +119,19 @@ public class ActivityTaskManagerServiceTests extends ActivityTestsBase { verify(lifecycleManager, times(0)).scheduleTransaction(any()); } + @Test(expected = IllegalStateException.class) + public void testOnPictureInPictureRequested_alreadyInPIPMode() throws RemoteException { + final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); + final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity(); + ClientLifecycleManager lifecycleManager = mService.getLifecycleManager(); + doReturn(true).when(activity).inPinnedWindowingMode(); + + mService.requestPictureInPictureMode(activity.token); + + // Check that no transactions with enter pip requests are made. + verify(lifecycleManager, times(0)).scheduleTransaction(any()); + } + @Test public void testDisplayWindowListener() { final ArrayList<Integer> added = new ArrayList<>(); diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt index de65ba24972b..a4d8353d1253 100644 --- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt +++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt @@ -44,7 +44,7 @@ class NetworkAgentConfigTest { setPartialConnectivityAcceptable(false) setUnvalidatedConnectivityAcceptable(true) }.build() - assertParcelSane(config, 9) + assertParcelSane(config, 10) } @Test @IgnoreUpTo(Build.VERSION_CODES.Q) |